From 87b36779569f11fe81db49e7a6e79ca0b0ad5286 Mon Sep 17 00:00:00 2001 From: joaquinpereyra98 <24190917+joaquinpereyra98@users.noreply.github.com> Date: Mon, 7 Jul 2025 20:27:21 -0300 Subject: [PATCH] Refactor/275 actor sheets simplification (#291) * FEAT: create isNPC geeter and add the prop on metada on actors FEAT: create common method for documents sheets FEAT: create BaseActorSheet and implementation * FIX: tabs label * REFACTOR: remove unused methods REFACTOR: simplify CharacterSheet's click actions methods REFACTOR: minor fix on DHActor class * REFACTOR: remove unused methods REFACTOR: create method on BaseActorSheet REFACTOR: make Datamodel metadata getter * REFACTOR: remove unused method on setting sheet FEAT: create BaseActorSetting FIX: add type="button" to button on actor's sheet * FIX jsdoc * PRETTIER --------- Co-authored-by: Joaquin Pereyra --- .../sheets-configs/adversary-settings.mjs | 146 +--- .../sheets-configs/companion-settings.mjs | 134 +-- .../sheets-configs/environment-settings.mjs | 143 +-- .../applications/sheets/actors/adversary.mjs | 125 +-- .../applications/sheets/actors/character.mjs | 825 ++++++------------ .../applications/sheets/actors/companion.mjs | 58 +- .../sheets/actors/environment.mjs | 93 +- module/applications/sheets/api/_modules.mjs | 2 + .../applications/sheets/api/actor-setting.mjs | 50 ++ .../sheets/api/application-mixin.mjs | 114 ++- module/applications/sheets/api/base-actor.mjs | 52 ++ module/applications/sheets/api/base-item.mjs | 20 +- .../applications/sheets/daggerheart-sheet.mjs | 2 + module/data/actor/adversary.mjs | 4 +- module/data/actor/base.mjs | 13 +- module/data/actor/character.mjs | 3 +- module/data/actor/companion.mjs | 4 +- module/data/actor/environment.mjs | 4 +- module/data/item/base.mjs | 9 +- module/documents/actor.mjs | 39 +- module/documents/item.mjs | 10 +- .../adversary-settings/experiences.hbs | 2 +- .../adversary-settings/features.hbs | 6 +- .../companion-settings/details.hbs | 4 +- .../environment-settings/adversaries.hbs | 16 +- .../environment-settings/features.hbs | 6 +- templates/sheets/actors/character/header.hbs | 29 +- templates/sheets/actors/character/loadout.hbs | 36 +- templates/sheets/actors/character/sidebar.hbs | 4 +- templates/sheets/actors/companion/header.hbs | 2 +- .../sheets/actors/environment/header.hbs | 2 +- .../global/partials/domain-card-item.hbs | 4 +- .../sheets/global/partials/inventory-item.hbs | 4 +- templates/sheets/global/tabs/tab-effects.hbs | 11 +- 34 files changed, 723 insertions(+), 1253 deletions(-) create mode 100644 module/applications/sheets/api/actor-setting.mjs create mode 100644 module/applications/sheets/api/base-actor.mjs diff --git a/module/applications/sheets-configs/adversary-settings.mjs b/module/applications/sheets-configs/adversary-settings.mjs index 4b849907..d95e6129 100644 --- a/module/applications/sheets-configs/adversary-settings.mjs +++ b/module/applications/sheets-configs/adversary-settings.mjs @@ -1,36 +1,15 @@ -const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; +import DHBaseActorSettings from '../sheets/api/actor-setting.mjs'; -export default class DHAdversarySettings extends HandlebarsApplicationMixin(ApplicationV2) { - constructor(actor) { - super({}); - - this.actor = actor; - this._dragDrop = this._createDragDropHandlers(); - } - - get title() { - return `${game.i18n.localize('DAGGERHEART.GENERAL.Tabs.settings')}`; - } +/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ +export default class DHAdversarySettings extends DHBaseActorSettings { + /**@inheritdoc */ static DEFAULT_OPTIONS = { - tag: 'form', - classes: ['daggerheart', 'dh-style', 'dialog', 'adversary-settings'], - window: { - icon: 'fa-solid fa-wrench', - resizable: false - }, + classes: ['adversary-settings'], position: { width: 455, height: 'auto' }, actions: { - addExperience: this.#addExperience, - removeExperience: this.#removeExperience, - addFeature: this.#addFeature, - editFeature: this.#editFeature, - removeFeature: this.#removeFeature - }, - form: { - handler: this.updateForm, - submitOnChange: true, - closeOnSubmit: false + addExperience: DHAdversarySettings.#addExperience, + removeExperience: DHAdversarySettings.#removeExperience }, dragDrop: [ { dragSelector: null, dropSelector: '.tab.features' }, @@ -38,6 +17,7 @@ export default class DHAdversarySettings extends HandlebarsApplicationMixin(Appl ] }; + /**@override */ static PARTS = { header: { id: 'header', @@ -62,116 +42,35 @@ export default class DHAdversarySettings extends HandlebarsApplicationMixin(Appl } }; + /** @override */ static TABS = { - details: { - active: true, - cssClass: '', - group: 'primary', - id: 'details', - icon: null, - label: 'DAGGERHEART.GENERAL.Tabs.details' - }, - attack: { - active: false, - cssClass: '', - group: 'primary', - id: 'attack', - icon: null, - label: 'DAGGERHEART.GENERAL.Tabs.attack' - }, - experiences: { - active: false, - cssClass: '', - group: 'primary', - id: 'experiences', - icon: null, - label: 'DAGGERHEART.GENERAL.Tabs.experiences' - }, - features: { - active: false, - cssClass: '', - group: 'primary', - id: 'features', - icon: null, - label: 'DAGGERHEART.GENERAL.Tabs.features' + primary: { + tabs: [{ id: 'details' }, { id: 'attack' }, { id: 'experiences' }, { id: 'features' }], + initial: 'details', + labelPrefix: 'DAGGERHEART.GENERAL.Tabs' } }; - async _prepareContext(_options) { - const context = await super._prepareContext(_options); - context.document = this.actor; - context.tabs = this._getTabs(this.constructor.TABS); - context.systemFields = this.actor.system.schema.fields; - context.systemFields.attack.fields = this.actor.system.attack.schema.fields; - context.isNPC = true; - - return context; - } - - _attachPartListeners(partId, htmlElement, options) { - super._attachPartListeners(partId, htmlElement, options); - - this._dragDrop.forEach(d => d.bind(htmlElement)); - } - - _createDragDropHandlers() { - return this.options.dragDrop.map(d => { - d.callbacks = { - dragstart: this._onDragStart.bind(this), - drop: this._onDrop.bind(this) - }; - return new foundry.applications.ux.DragDrop.implementation(d); - }); - } - - _getTabs(tabs) { - for (const v of Object.values(tabs)) { - v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active; - v.cssClass = v.active ? 'active' : ''; - } - - return tabs; - } + /* -------------------------------------------- */ + /** + * Adds a new experience entry to the actor. + * @type {ApplicationClickAction} + */ static async #addExperience() { const newExperience = { name: 'Experience', modifier: 0 }; await this.actor.update({ [`system.experiences.${foundry.utils.randomID()}`]: newExperience }); - this.render(); } + /** + * Removes an experience entry from the actor. + * @type {ApplicationClickAction} + */ static async #removeExperience(_, target) { await this.actor.update({ [`system.experiences.-=${target.dataset.experience}`]: null }); - this.render(); - } - - static async #addFeature(_, _button) { - await this.actor.createEmbeddedDocuments('Item', [ - { - type: 'feature', - name: game.i18n.format('DOCUMENT.New', { type: game.i18n.localize('TYPES.Item.feature') }), - img: 'icons/skills/melee/weapons-crossed-swords-black.webp' - } - ]); - this.render(); - } - - static async #editFeature(event, target) { - event.stopPropagation(); - this.actor.items.get(target.id).sheet.render(true); - } - - static async #removeFeature(event, target) { - event.stopPropagation(); - await this.actor.deleteEmbeddedDocuments('Item', [target.id]); - this.render(); - } - - static async updateForm(event, _, formData) { - await this.actor.update(formData.object); - this.render(); } async _onDragStart(event) { @@ -192,7 +91,6 @@ export default class DHAdversarySettings extends HandlebarsApplicationMixin(Appl const item = await fromUuid(data.uuid); if (item.type === 'feature') { await this.actor.createEmbeddedDocuments('Item', [item]); - this.render(); } } } diff --git a/module/applications/sheets-configs/companion-settings.mjs b/module/applications/sheets-configs/companion-settings.mjs index e25b401d..fed70c56 100644 --- a/module/applications/sheets-configs/companion-settings.mjs +++ b/module/applications/sheets-configs/companion-settings.mjs @@ -1,37 +1,20 @@ import { GMUpdateEvent, socketEvent } from '../../systemRegistration/socket.mjs'; import DhCompanionlevelUp from '../levelup/companionLevelup.mjs'; +import DHBaseActorSettings from '../sheets/api/actor-setting.mjs'; -const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; - -export default class DHCompanionSettings extends HandlebarsApplicationMixin(ApplicationV2) { - constructor(actor) { - super({}); - - this.actor = actor; - } - - get title() { - return `${game.i18n.localize('DAGGERHEART.GENERAL.Tabs.settings')}`; - } +/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ +export default class DHCompanionSettings extends DHBaseActorSettings { + /**@inheritdoc */ static DEFAULT_OPTIONS = { - tag: 'form', - classes: ['daggerheart', 'dh-style', 'dialog', 'companion-settings'], - window: { - icon: 'fa-solid fa-wrench', - resizable: false - }, + classes: ['companion-settings'], position: { width: 455, height: 'auto' }, actions: { - levelUp: this.levelUp - }, - form: { - handler: this.updateForm, - submitOnChange: true, - closeOnSubmit: false + levelUp: DHCompanionSettings.#levelUp } }; + /**@inheritdoc */ static PARTS = { header: { id: 'header', @@ -52,109 +35,64 @@ export default class DHCompanionSettings extends HandlebarsApplicationMixin(Appl } }; + /** @inheritdoc */ static TABS = { - details: { - active: true, - cssClass: '', - group: 'primary', - id: 'details', - icon: null, - label: 'DAGGERHEART.GENERAL.Tabs.details' - }, - experiences: { - active: false, - cssClass: '', - group: 'primary', - id: 'experiences', - icon: null, - label: 'DAGGERHEART.GENERAL.Tabs.experiences' - }, - attack: { - active: false, - cssClass: '', - group: 'primary', - id: 'attack', - icon: null, - label: 'DAGGERHEART.GENERAL.Tabs.attack' + primary: { + tabs: [{ id: 'details' }, { id: 'attack' }, { id: 'experiences' }], + initial: 'details', + labelPrefix: 'DAGGERHEART.GENERAL.Tabs' } }; - _attachPartListeners(partId, htmlElement, options) { - super._attachPartListeners(partId, htmlElement, options); - - htmlElement.querySelector('.partner-value')?.addEventListener('change', this.onPartnerChange.bind(this)); + /**@inheritdoc */ + async _onRender(context, options) { + await super._onRender(context, options); + this.element.querySelector('.partner-value')?.addEventListener('change', this.onPartnerChange.bind(this)); } + /**@inheritdoc */ async _prepareContext(_options) { const context = await super._prepareContext(_options); - context.document = this.actor; - context.tabs = this._getTabs(this.constructor.TABS); - context.systemFields = this.actor.system.schema.fields; - context.systemFields.attack.fields = this.actor.system.attack.schema.fields; - context.isNPC = true; + context.playerCharacters = game.actors - .filter( - x => - x.type === 'character' && - (x.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER) || - this.document.system.partner?.uuid === x.uuid) - ) + .filter(x => x.type === 'character' && (x.isOwner || this.document.system.partner?.uuid === x.uuid)) .map(x => ({ key: x.uuid, name: x.name })); return context; } - _getTabs(tabs) { - for (const v of Object.values(tabs)) { - v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active; - v.cssClass = v.active ? 'active' : ''; - } - - return tabs; - } - + /** + * Handles changes to the actor's partner selection. + * @param {Event} event - The change event triggered by the partner input element. + */ async onPartnerChange(event) { - const partnerDocument = event.target.value - ? await foundry.utils.fromUuid(event.target.value) - : this.actor.system.partner; - const partnerUpdate = { 'system.companion': event.target.value ? this.actor.uuid : null }; + const value = event.target.value; + const partnerDocument = value ? await foundry.utils.fromUuid(value) : this.actor.system.partner; + const partnerUpdate = { 'system.companion': value ? this.actor.uuid : null }; - if (!partnerDocument.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER)) { + if (!partnerDocument.isOwner) { await game.socket.emit(`system.${CONFIG.DH.id}`, { action: socketEvent.GMUpdate, data: { action: GMUpdateEvent.UpdateDocument, uuid: partnerDocument.uuid, - update: update + update: partnerUpdate } }); } else { await partnerDocument.update(partnerUpdate); } - await this.actor.update({ 'system.partner': event.target.value }); + await this.actor.update({ 'system.partner': value }); - if (!event.target.value) { - await this.actor.updateLevel(1); - } - - this.render(); + if (!value) await this.actor.updateLevel(1); } - async viewActor(_, button) { - const target = button.closest('[data-item-uuid]'); - const actor = await foundry.utils.fromUuid(target.dataset.itemUuid); - if (!actor) return; - - actor.sheet.render(true); - } - - static async levelUp() { - new DhCompanionlevelUp(this.actor).render(true); - } - - static async updateForm(event, _, formData) { - await this.actor.update(formData.object); - this.render(); + /** + * Opens the companion level-up dialog for the associated actor. + * @type {ApplicationClickAction} + */ + static async #levelUp() { + new DhCompanionlevelUp(this.actor).render({ force: true }); } } diff --git a/module/applications/sheets-configs/environment-settings.mjs b/module/applications/sheets-configs/environment-settings.mjs index 69ae7f87..d0ca897a 100644 --- a/module/applications/sheets-configs/environment-settings.mjs +++ b/module/applications/sheets-configs/environment-settings.mjs @@ -1,39 +1,17 @@ -const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; +import DHBaseActorSettings from '../sheets/api/actor-setting.mjs'; -export default class DHEnvironmentSettings extends HandlebarsApplicationMixin(ApplicationV2) { - constructor(actor) { - super({}); - - this.actor = actor; - this._dragDrop = this._createDragDropHandlers(); - } - - get title() { - return `${game.i18n.localize('DAGGERHEART.GENERAL.Tabs.settings')}`; - } +/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ +export default class DHEnvironmentSettings extends DHBaseActorSettings { + /**@inheritdoc */ static DEFAULT_OPTIONS = { - tag: 'form', - classes: ['daggerheart', 'dh-style', 'dialog', 'environment-settings'], - window: { - icon: 'fa-solid fa-wrench', - resizable: false - }, - position: { width: 455, height: 'auto' }, + classes: ['environment-settings'], actions: { - addFeature: this.#addFeature, - editFeature: this.#editFeature, - removeFeature: this.#removeFeature, - addCategory: this.#addCategory, - deleteProperty: this.#deleteProperty, + addCategory: DHEnvironmentSettings.#addCategory, + removeCategory: DHEnvironmentSettings.#removeCategory, viewAdversary: this.#viewAdversary, deleteAdversary: this.#deleteAdversary }, - form: { - handler: this.updateForm, - submitOnChange: true, - closeOnSubmit: false - }, dragDrop: [ { dragSelector: null, dropSelector: '.category-container' }, { dragSelector: null, dropSelector: '.tab.features' }, @@ -41,6 +19,7 @@ export default class DHEnvironmentSettings extends HandlebarsApplicationMixin(Ap ] }; + /**@override */ static PARTS = { header: { id: 'header', @@ -61,100 +40,33 @@ export default class DHEnvironmentSettings extends HandlebarsApplicationMixin(Ap } }; + /** @inheritdoc */ static TABS = { - details: { - active: true, - cssClass: '', - group: 'primary', - id: 'details', - icon: null, - label: 'DAGGERHEART.GENERAL.Tabs.details' - }, - features: { - active: false, - cssClass: '', - group: 'primary', - id: 'features', - icon: null, - label: 'DAGGERHEART.GENERAL.Tabs.features' - }, - adversaries: { - active: false, - cssClass: '', - group: 'primary', - id: 'adversaries', - icon: null, - label: 'DAGGERHEART.GENERAL.Tabs.adversaries' + primary: { + tabs: [{ id: 'details' }, { id: 'features' }, { id: 'adversaries' }], + initial: 'details', + labelPrefix: 'DAGGERHEART.GENERAL.Tabs' } }; - async _prepareContext(_options) { - const context = await super._prepareContext(_options); - context.document = this.actor; - context.tabs = this._getTabs(this.constructor.TABS); - context.systemFields = this.actor.system.schema.fields; - context.isNPC = true; - - return context; - } - - _attachPartListeners(partId, htmlElement, options) { - super._attachPartListeners(partId, htmlElement, options); - - this._dragDrop.forEach(d => d.bind(htmlElement)); - } - - _createDragDropHandlers() { - return this.options.dragDrop.map(d => { - d.callbacks = { - dragstart: this._onDragStart.bind(this), - drop: this._onDrop.bind(this) - }; - return new foundry.applications.ux.DragDrop.implementation(d); - }); - } - - _getTabs(tabs) { - for (const v of Object.values(tabs)) { - v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active; - v.cssClass = v.active ? 'active' : ''; - } - - return tabs; - } - - static async #addFeature(_, _button) { - await this.actor.createEmbeddedDocuments('Item', [ - { - type: 'feature', - name: game.i18n.format('DOCUMENT.New', { type: game.i18n.localize('TYPES.Item.feature') }), - img: 'icons/magic/perception/orb-crystal-ball-scrying-blue.webp' - } - ]); - this.render(); - } - - static async #editFeature(_, target) { - this.actor.items.get(target.id).sheet.render(true); - } - - static async #removeFeature(_, target) { - await this.actor.deleteEmbeddedDocuments('Item', [target.id]); - this.render(); - } - + /** + * Adds a new category entry to the actor. + * @type {ApplicationClickAction} + */ static async #addCategory() { await this.actor.update({ [`system.potentialAdversaries.${foundry.utils.randomID()}.label`]: game.i18n.localize( 'DAGGERHEART.ACTORS.Environment.newAdversary' ) }); - this.render(); } - static async #deleteProperty(_, target) { - await this.actor.update({ [`${target.dataset.path}.-=${target.id}`]: null }); - this.render(); + /** + * Removes an category entry from the actor. + * @type {ApplicationClickAction} + */ + static async #removeCategory(_, target) { + await this.actor.update({ [`system.potentialAdversaries.-=${target.dataset.categoryId}`]: null }); } static async #viewAdversary(_, button) { @@ -164,17 +76,17 @@ export default class DHEnvironmentSettings extends HandlebarsApplicationMixin(Ap return; } - adversary.sheet.render(true); + adversary.sheet.render({ force: true }); } static async #deleteAdversary(event, target) { const adversaryKey = target.dataset.adversary; const path = `system.potentialAdversaries.${target.dataset.potentialAdversary}.adversaries`; + console.log(target.dataset.potentialAdversar); const newAdversaries = foundry.utils .getProperty(this.actor, path) .filter(x => x && (x?.uuid ?? x) !== adversaryKey); await this.actor.update({ [path]: newAdversaries }); - this.render(); } async _onDragStart(event) { @@ -206,9 +118,4 @@ export default class DHEnvironmentSettings extends HandlebarsApplicationMixin(Ap this.render(); } } - - static async updateForm(event, _, formData) { - await this.actor.update(formData.object); - this.render(); - } } diff --git a/module/applications/sheets/actors/adversary.mjs b/module/applications/sheets/actors/adversary.mjs index 97d25c36..5dae9741 100644 --- a/module/applications/sheets/actors/adversary.mjs +++ b/module/applications/sheets/actors/adversary.mjs @@ -1,29 +1,19 @@ -import DHActionConfig from '../../sheets-configs/action-config.mjs'; -import DaggerheartSheet from '../daggerheart-sheet.mjs'; -import DHAdversarySettings from '../../sheets-configs/adversary-settings.mjs'; +import DHBaseActorSheet from '../api/base-actor.mjs'; -const { ActorSheetV2 } = foundry.applications.sheets; -export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) { +/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ + +export default class AdversarySheet extends DHBaseActorSheet { static DEFAULT_OPTIONS = { - tag: 'form', - classes: ['daggerheart', 'sheet', 'actor', 'dh-style', 'adversary'], + classes: ['adversary'], position: { width: 660, height: 766 }, window: { resizable: true }, actions: { - reactionRoll: this.reactionRoll, + reactionRoll: AdversarySheet.#reactionRoll, useItem: this.useItem, - toChat: this.toChat, - attackConfigure: this.attackConfigure, - addExperience: this.addExperience, - removeExperience: this.removeExperience, - toggleHP: this.toggleHP, - toggleStress: this.toggleStress, - openSettings: this.openSettings + toChat: this.toChat }, - form: { - handler: this.updateForm, - submitOnChange: true, - closeOnSubmit: false + window: { + resizable: true } }; @@ -35,40 +25,19 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) { effects: { template: 'systems/daggerheart/templates/sheets/actors/adversary/effects.hbs' } }; + /** @inheritdoc */ static TABS = { - features: { - active: true, - cssClass: '', - group: 'primary', - id: 'features', - icon: null, - label: 'DAGGERHEART.GENERAL.Tabs.features' - }, - notes: { - active: false, - cssClass: '', - group: 'primary', - id: 'notes', - icon: null, - label: 'DAGGERHEART.GENERAL.Tabs.notes' - }, - effects: { - active: false, - cssClass: '', - group: 'primary', - id: 'effects', - icon: null, - label: 'DAGGERHEART.GENERAL.Tabs.effects' + primary: { + tabs: [{ id: 'features' }, { id: 'notes' }, { id: 'effects' }], + initial: 'features', + labelPrefix: 'DAGGERHEART.GENERAL.Tabs' } }; - async _prepareContext(_options) { - const context = await super._prepareContext(_options); - context.document = this.document; - context.tabs = super._getTabs(this.constructor.TABS); + /**@inheritdoc */ + async _prepareContext(options) { + const context = await super._prepareContext(options); context.systemFields.attack.fields = this.document.system.attack.schema.fields; - context.getEffectDetails = this.getEffectDetails.bind(this); - context.isNPC = true; return context; } @@ -78,18 +47,20 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) { return item; } - static async updateForm(event, _, formData) { - await this.document.update(formData.object); - this.render(); - } + /* -------------------------------------------- */ + /* Application Clicks Actions */ + /* -------------------------------------------- */ - static async reactionRoll(event) { + /** + * Performs a reaction roll for an Adversary. + * @type {ApplicationClickAction} + */ + static #reactionRoll(event) { const config = { event: event, title: `Reaction Roll: ${this.actor.name}`, headerTitle: 'Adversary Reaction Roll', roll: { - // modifier: null, type: 'reaction' }, chatMessage: { @@ -98,22 +69,23 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) { mute: true } }; + this.actor.diceRoll(config); } - getEffectDetails(id) { - return {}; - } - - static async openSettings() { - await new DHAdversarySettings(this.document).render(true); - } - + /** + * + * @type {ApplicationClickAction} + */ static async useItem(event) { const action = this.getItem(event) ?? this.actor.system.attack; action.use(event); } + /** + * + * @type {ApplicationClickAction} + */ static async toChat(event, button) { if (button?.dataset?.type === 'experience') { const experience = this.document.system.experiences[button.dataset.uuid]; @@ -140,33 +112,4 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) { item.toChat(this.document.id); } } - - static async attackConfigure(event) { - await new DHActionConfig(this.document.system.attack).render(true); - } - - static async addExperience() { - const experienceId = foundry.utils.randomID(); - await this.document.update({ - [`system.experiences.${experienceId}`]: { id: experienceId, name: 'Experience', value: 1 } - }); - } - - static async removeExperience(_, button) { - await this.document.update({ - [`system.experiences.-=${button.dataset.experience}`]: null - }); - } - - static async toggleHP(_, button) { - const index = Number.parseInt(button.dataset.index); - const newHP = index < this.document.system.resources.health.value ? index : index + 1; - await this.document.update({ 'system.resources.health.value': newHP }); - } - - static async toggleStress(_, button) { - const index = Number.parseInt(button.dataset.index); - const newStress = index < this.document.system.resources.stress.value ? index : index + 1; - await this.document.update({ 'system.resources.stress.value': newStress }); - } } diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index 5a5120a6..02e693d7 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -1,66 +1,48 @@ -import { capitalize } from '../../../helpers/utils.mjs'; +import DHBaseActorSheet from '../api/base-actor.mjs'; import DhpDeathMove from '../../dialogs/deathMove.mjs'; -import DhpDowntime from '../../dialogs/downtime.mjs'; -import DaggerheartSheet from '.././daggerheart-sheet.mjs'; import { abilities } from '../../../config/actorConfig.mjs'; import DhCharacterlevelUp from '../../levelup/characterLevelup.mjs'; import DhCharacterCreation from '../../characterCreation/characterCreation.mjs'; import FilterMenu from '../../ux/filter-menu.mjs'; -import DHActionConfig from '../../sheets-configs/action-config.mjs'; -const { ActorSheetV2 } = foundry.applications.sheets; +/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ + const { TextEditor } = foundry.applications.ux; -export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { - constructor(options = {}) { - super(options); - } - +export default class CharacterSheet extends DHBaseActorSheet { + /**@inheritdoc */ static DEFAULT_OPTIONS = { - tag: 'form', - classes: ['daggerheart', 'sheet', 'actor', 'dh-style', 'daggerheart', 'character'], + classes: ['character'], position: { width: 850, height: 800 }, actions: { - attributeRoll: this.rollAttribute, - toggleMarks: this.toggleMarks, - toggleHP: this.toggleHP, - toggleStress: this.toggleStress, - toggleHope: this.toggleHope, - toggleGold: this.toggleGold, - toggleLoadoutView: this.toggleLoadoutView, - attackRoll: this.attackRoll, - useDomainCard: this.useDomainCard, - selectClass: this.selectClass, - selectSubclass: this.selectSubclass, - selectCommunity: this.selectCommunity, - viewObject: this.viewObject, - useItem: this.useItem, - useFeature: this.useFeature, - takeShortRest: this.takeShortRest, - takeLongRest: this.takeLongRest, - deleteItem: this.deleteItem, - deleteScar: this.deleteScar, - makeDeathMove: this.makeDeathMove, - itemQuantityDecrease: (_, button) => this.setItemQuantity(button, -1), - itemQuantityIncrease: (_, button) => this.setItemQuantity(button, 1), - toChat: this.toChat, - useAdvancementCard: this.useAdvancementCard, - useAdvancementAbility: this.useAdvancementAbility, - toggleEquipItem: this.toggleEquipItem, - toggleVault: this.toggleVault, - levelManagement: this.levelManagement, - editImage: this._onEditImage, - triggerContextMenu: this.triggerContextMenu + triggerContextMenu: CharacterSheet.#triggerContextMenu, + toggleVault: CharacterSheet.#toggleVault, + rollAttribute: CharacterSheet.#rollAttribute, + toggleHope: CharacterSheet.#toggleHope, + toggleLoadoutView: CharacterSheet.#toggleLoadoutView, + openPack: CharacterSheet.#openPack, + makeDeathMove: CharacterSheet.#makeDeathMove, + levelManagement: CharacterSheet.#levelManagement, + toggleEquipItem: CharacterSheet.#toggleEquipItem, + useItem: this.useItem, //TODO Fix this + toChat: this.toChat }, window: { resizable: true }, - form: { - submitOnChange: true, - closeOnSubmit: false - }, - dragDrop: [] + dragDrop: [], + contextMenus: [ + { + handler: CharacterSheet._getContextMenuOptions, + selector: '[data-item-id]', + options: { + parentClassHooks: false, + fixed: true + } + } + ] }; + /**@override */ static PARTS = { sidebar: { id: 'sidebar', @@ -92,217 +74,31 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { } }; + /* -------------------------------------------- */ + + /** @inheritdoc */ static TABS = { - features: { - active: true, - cssClass: '', - group: 'primary', - id: 'features', - icon: null, - label: 'DAGGERHEART.GENERAL.Tabs.features' - }, - loadout: { - active: false, - cssClass: '', - group: 'primary', - id: 'loadout', - icon: null, - label: 'DAGGERHEART.GENERAL.Tabs.loadout' - }, - inventory: { - active: false, - cssClass: '', - group: 'primary', - id: 'inventory', - icon: null, - label: 'DAGGERHEART.GENERAL.Tabs.inventory' - }, - biography: { - active: false, - cssClass: '', - group: 'primary', - id: 'biography', - icon: null, - label: 'DAGGERHEART.GENERAL.Tabs.biography' - }, - effects: { - active: false, - cssClass: '', - group: 'primary', - id: 'effects', - icon: null, - label: 'DAGGERHEART.GENERAL.Tabs.effects' + primary: { + tabs: [{ id: 'features' }, { id: 'loadout' }, { id: 'inventory' }, { id: 'biography' }, { id: 'effects' }], + initial: 'features', + labelPrefix: 'DAGGERHEART.GENERAL.Tabs' } }; - _getTabs() { - const setActive = tabs => { - for (const v of Object.values(tabs)) { - v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active; - v.cssClass = v.active ? 'active' : ''; - } - }; - - const primaryTabs = { - features: { - active: true, - cssClass: '', - group: 'primary', - id: 'features', - icon: null, - label: game.i18n.localize('DAGGERHEART.GENERAL.Tabs.features') - }, - loadout: { - active: false, - cssClass: '', - group: 'primary', - id: 'loadout', - icon: null, - label: game.i18n.localize('DAGGERHEART.GENERAL.Tabs.loadout') - }, - inventory: { - active: false, - cssClass: '', - group: 'primary', - id: 'inventory', - icon: null, - label: game.i18n.localize('DAGGERHEART.GENERAL.Tabs.inventory') - }, - story: { - active: false, - cssClass: '', - group: 'primary', - id: 'story', - icon: null, - label: game.i18n.localize('DAGGERHEART.GENERAL.Tabs.story') - } - }; - const secondaryTabs = { - foundation: { - active: true, - cssClass: '', - group: 'secondary', - id: 'foundation', - icon: null, - label: game.i18n.localize('DAGGERHEART.GENERAL.Tabs.foundation') - }, - loadout: { - active: false, - cssClass: '', - group: 'secondary', - id: 'loadout', - icon: null, - label: game.i18n.localize('DAGGERHEART.GENERAL.Tabs.loadout') - }, - vault: { - active: false, - cssClass: '', - group: 'secondary', - id: 'vault', - icon: null, - label: game.i18n.localize('DAGGERHEART.GENERAL.Tabs.vault') - } - }; - - setActive(primaryTabs); - setActive(secondaryTabs); - - return { primary: primaryTabs, secondary: secondaryTabs }; - } - - /**@inheritdoc */ - async _onFirstRender(context, options) { - await super._onFirstRender(context, options); - - this._createContextMenues(); - } - /** @inheritDoc */ async _onRender(context, options) { await super._onRender(context, options); + this.element + .querySelector('.level-value') + ?.addEventListener('change', event => this.document.updateLevel(Number(event.currentTarget.value))); + this._createFilterMenus(); this._createSearchFilter(); } /* -------------------------------------------- */ - _createContextMenues() { - const allOptions = { - useItem: { - name: 'DAGGERHEART.GENERAL.use', - icon: '', - condition: el => { - const item = this.getItem(el); - return !['class', 'subclass'].includes(item.type); - }, - callback: (button, event) => this.constructor.useItem.bind(this)(event, button) - }, - equip: { - name: 'DAGGERHEART.ACTORS.Character.contextMenu.equip', - icon: '', - condition: el => { - const item = this.getItem(el); - return ['weapon', 'armor'].includes(item.type) && !item.system.equipped; - }, - callback: this.constructor.toggleEquipItem.bind(this) - }, - unequip: { - name: 'DAGGERHEART.ACTORS.Character.contextMenu.unequip', - icon: '', - condition: el => { - const item = this.getItem(el); - return ['weapon', 'armor'].includes(item.type) && item.system.equipped; - }, - callback: this.constructor.toggleEquipItem.bind(this) - }, - sendToLoadout: { - name: 'DAGGERHEART.ACTORS.Character.contextMenu.toLoadout', - icon: '', - condition: el => { - const item = this.getItem(el); - return ['domainCard'].includes(item.type) && item.system.inVault; - }, - callback: this.constructor.toggleVault.bind(this) - }, - sendToVault: { - name: 'DAGGERHEART.ACTORS.Character.contextMenu.toVault', - icon: '', - condition: el => { - const item = this.getItem(el); - return ['domainCard'].includes(item.type) && !item.system.inVault; - }, - callback: this.constructor.toggleVault.bind(this) - }, - sendToChat: { - name: 'DAGGERHEART.ACTORS.Character.contextMenu.sendToChat', - icon: '', - callback: this.constructor.toChat.bind(this) - }, - edit: { - name: 'CONTROLS.CommonEdit', - icon: '', - callback: this.constructor.viewObject.bind(this) - }, - delete: { - name: 'CONTROLS.CommonDelete', - icon: '', - callback: this.constructor.deleteItem.bind(this) - } - }; - - this._createContextMenu(() => Object.values(allOptions), `[data-item-id]`, { - parentClassHooks: false, - fixed: true - }); - } - - _attachPartListeners(partId, htmlElement, options) { - super._attachPartListeners(partId, htmlElement, options); - - htmlElement.querySelector('.level-value')?.addEventListener('change', this.onLevelChange.bind(this)); - } - getItem(element) { const listElement = (element.target ?? element).closest('[data-item-id]'); const itemId = listElement.dataset.itemId; @@ -315,27 +111,12 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { } } - static triggerContextMenu(event, button) { - return CONFIG.ux.ContextMenu.triggerContextMenu(event); - } - - static _onEditImage() { - const fp = new foundry.applications.apps.FilePicker.implementation({ - current: this.document.img, - type: 'image', - redirectToRoot: ['icons/svg/mystery-man.svg'], - callback: async path => this._updateImage.bind(this)(path), - top: this.position.top + 40, - left: this.position.left + 10 - }); - return fp.browse(); - } + /* -------------------------------------------- */ + /* Prepare Context */ + /* -------------------------------------------- */ async _prepareContext(_options) { const context = await super._prepareContext(_options); - context.document = this.document; - context.tabs = super._getTabs(this.constructor.TABS); - context.config = CONFIG.DH; context.attributes = Object.keys(this.document.system.traits).reduce((acc, key) => { acc[key] = { @@ -369,6 +150,109 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { return context; } + /**@inheritdoc */ + async _preparePartContext(partId, context, options) { + context = await super._preparePartContext(partId, context, options); + switch (partId) { + case 'loadout': + await this._prepareLoadoutContext(context, options); + break; + case 'sidebar': + await this._prepareSidebarContext(context, options); + break; + } + return context; + } + + async _prepareLoadoutContext(context, _options) { + context.listView = game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.displayDomainCardsAsList); + } + + async _prepareSidebarContext(context, _options) { + context.isDeath = this.document.system.deathMoveViable; + } + + /* -------------------------------------------- */ + /* Context Menu */ + /* -------------------------------------------- */ + + /** + * Get the set of ContextMenu options. + * @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance + * @this {CharacterSheet} + * @protected + */ + static _getContextMenuOptions() { + /** + * Get the item from the element. + * @param {HTMLElement} el + * @returns {foundry.documents.Item?} + */ + const getItem = el => this.actor.items.get(el.closest('[data-item-id]')?.dataset.itemId); + + return [ + { + name: 'DAGGERHEART.Sheets.PC.ContextMenu.UseItem', + icon: '', + condition: el => { + const item = getItem(el); + return !['class', 'subclass'].includes(item.type); + }, + callback: (button, event) => CharacterSheet.useItem.call(this, event, button) + }, + { + name: 'DAGGERHEART.Sheets.PC.ContextMenu.Equip', + icon: '', + condition: el => { + const item = getItem(el); + return ['weapon', 'armor'].includes(item.type) && !item.system.equipped; + }, + callback: CharacterSheet.#toggleEquipItem.bind(this) + }, + { + name: 'DAGGERHEART.Sheets.PC.ContextMenu.Unequip', + icon: '', + condition: el => { + const item = getItem(el); + return ['weapon', 'armor'].includes(item.type) && item.system.equipped; + }, + callback: CharacterSheet.#toggleEquipItem.bind(this) + }, + { + name: 'DAGGERHEART.Sheets.PC.ContextMenu.ToLoadout', + icon: '', + condition: el => { + const item = getItem(el); + return ['domainCard'].includes(item.type) && item.system.inVault; + }, + callback: target => getItem(target).update({ 'system.inVault': false }) + }, + { + name: 'DAGGERHEART.Sheets.PC.ContextMenu.ToVault', + icon: '', + condition: el => { + const item = getItem(el); + return ['domainCard'].includes(item.type) && !item.system.inVault; + }, + callback: target => getItem(target).update({ 'system.inVault': true }) + }, + { + name: 'DAGGERHEART.Sheets.PC.ContextMenu.SendToChat', + icon: '', + callback: CharacterSheet.toChat.bind(this) + }, + { + name: 'DAGGERHEART.Sheets.PC.ContextMenu.Edit', + icon: '', + callback: target => getItem(target).sheet.render({ force: true }) + }, + { + name: 'DAGGERHEART.Sheets.PC.ContextMenu.Delete', + icon: '', + callback: el => getItem(el).delete() + } + ]; + } /* -------------------------------------------- */ /* Filter Tracking */ /* -------------------------------------------- */ @@ -488,22 +372,7 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { li.hidden = !(menu.has(item.id) && matchesSearch); } } - - static async rollAttribute(event, button) { - const abilityLabel = game.i18n.localize(abilities[button.dataset.attribute].label); - const config = { - event: event, - title: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`, - headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { - ability: abilityLabel - }), - roll: { - trait: button.dataset.attribute - } - }; - this.document.diceRoll(config); - } - + /* -------------------------------------------- */ /* Filter Menus */ /* -------------------------------------------- */ @@ -579,33 +448,56 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { } } + /* -------------------------------------------- */ + /* Application Clicks Actions */ /* -------------------------------------------- */ - async mapFeatureType(data, configType) { - return await Promise.all( - data.map(async x => { - const abilities = x.system.abilities - ? await Promise.all(x.system.abilities.map(async x => await fromUuid(x.uuid))) - : []; + /** + * Opens the character level management window. + * If the character requires setup, opens the character creation interface. + * If class or subclass is missing, shows an error notification. + * @type {ApplicationClickAction} + */ + static #levelManagement() { + if (this.document.system.needsCharacterSetup) + return new DhCharacterCreation(this.document).render({ force: true }); - return { - ...x, - uuid: x.uuid, - system: { - ...x.system, - abilities: abilities, - type: game.i18n.localize(configType[x.system.type ?? x.type].label) - } - }; - }) - ); + const { value, subclass } = this.document.system.class; + if (!value || !subclass) + return ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.missingClassOrSubclass')); + + new DhCharacterlevelUp(this.document).render({ force: true }); } - static async rollAttribute(event, button) { + /** + * Opens the Death Move interface for the character. + * @type {ApplicationClickAction} + */ + static async #makeDeathMove() { + await new DhpDeathMove(this.document).render({ force: true }); + } + + /** + * Opens a compendium pack given its dataset key. + * @type {ApplicationClickAction} + */ + static async #openPack(_event, button) { + const { key } = button.dataset; + game.packs.get(key)?.render(true); + } + + /** + * Rolls an attribute check based on the clicked button's dataset attribute. + * @type {ApplicationClickAction} + */ + static async #rollAttribute(event, button) { const abilityLabel = game.i18n.localize(abilities[button.dataset.attribute].label); const config = { event: event, - title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { ability: abilityLabel }), + title: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`, + headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilitychecktitle', { + ability: abilityLabel + }), roll: { trait: button.dataset.attribute } @@ -613,120 +505,78 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { this.document.diceRoll(config); } - static async toggleMarks(_, button) { - const markValue = Number.parseInt(button.dataset.value); - const newValue = this.document.system.armor.system.marks.value >= markValue ? markValue - 1 : markValue; - await this.document.system.armor.update({ 'system.marks.value': newValue }); + /** + * Toggles the equipped state of an item (armor or weapon). + * @type {ApplicationClickAction} + */ + static async #toggleEquipItem(_event, button) { + //TODO: redo this + const item = this.actor.items.get(button.closest('[data-item-id]')?.dataset.itemId); + if (!item) return; + if (item.system.equipped) { + await item.update({ 'system.equipped': false }); + return; + } + + switch (item.type) { + case 'armor': + const currentArmor = this.document.system.armor; + if (currentArmor) { + await currentArmor.update({ 'system.equipped': false }); + } + + await item.update({ 'system.equipped': true }); + break; + case 'weapon': + await this.document.system.constructor.unequipBeforeEquip.bind(this.document.system)(item); + + await item.update({ 'system.equipped': true }); + break; + } } - static async toggleHP(_, button) { - const healthValue = Number.parseInt(button.dataset.value); - const newValue = this.document.system.resources.hitPoints.value >= healthValue ? healthValue - 1 : healthValue; - await this.document.update({ 'system.resources.hitPoints.value': newValue }); + /** + * Toggles the current view of the character's loadout display. + * @type {ApplicationClickAction} + */ + static async #toggleLoadoutView(_, button) { + const newAbilityView = button.dataset.value !== 'true'; + await game.user.setFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.displayDomainCardsAsList, newAbilityView); + this.render(); } - static async toggleStress(_, button) { - const healthValue = Number.parseInt(button.dataset.value); - const newValue = this.document.system.resources.stress.value >= healthValue ? healthValue - 1 : healthValue; - await this.document.update({ 'system.resources.stress.value': newValue }); - } - - static async toggleHope(_, button) { + /** + * Toggles a hope resource value. + * @type {ApplicationClickAction} + */ + static async #toggleHope(_, button) { const hopeValue = Number.parseInt(button.dataset.value); const newValue = this.document.system.resources.hope.value >= hopeValue ? hopeValue - 1 : hopeValue; await this.document.update({ 'system.resources.hope.value': newValue }); } - static async toggleGold(_, button) { - const goldValue = Number.parseInt(button.dataset.value); - const goldType = button.dataset.type; - const newValue = this.document.system.gold[goldType] >= goldValue ? goldValue - 1 : goldValue; - - const update = `system.gold.${goldType}`; - await this.document.update({ [update]: newValue }); + /** + * Toggles whether an item is stored in the vault. + * @type {ApplicationClickAction} + */ + static async #toggleVault(event, button) { + const docId = button.closest('[data-item-id]')?.dataset.itemId; + const doc = this.document.items.get(docId); + await doc?.update({ 'system.inVault': !doc.system.inVault }); } - static async toggleLoadoutView(_, button) { - const newAbilityView = !(button.dataset.value === 'true'); - await game.user.setFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.displayDomainCardsAsList, newAbilityView); - this.render(); - } - - static async toggleLoadoutView(_, button) { - const newAbilityView = !(button.dataset.value === 'true'); - await game.user.setFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.displayDomainCardsAsList, newAbilityView); - this.render(); - } - - static async attackRoll(event, button) { - const weapon = await fromUuid(button.dataset.weapon); - if (!weapon) return; - - const wasUsed = await weapon.use(event); - if (wasUsed) { - Hooks.callAll(CONFIG.DH.HOOKS.characterAttack, {}); - } - } - - static levelManagement() { - if (this.document.system.needsCharacterSetup) { - this.characterSetup(); - } else { - this.openLevelUp(); - } - } - - characterSetup() { - new DhCharacterCreation(this.document).render(true); - } - - openLevelUp() { - if (!this.document.system.class.value || !this.document.system.class.subclass) { - ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.missingClassOrSubclass')); - return; - } - - new DhCharacterlevelUp(this.document).render(true); - } - - static async useDomainCard(event, button) { - const card = this.getItem(event); - if (!card) return; - - const cls = getDocumentClass('ChatMessage'); - const systemData = { - title: `${game.i18n.localize('DAGGERHEART.UI.Chat.domainCard.title')} - ${capitalize(button.dataset.domain)}`, - origin: this.document.id, - img: card.img, - name: card.name, - description: card.system.effect, - actions: card.system.actions - }; - const msg = new cls({ - type: 'abilityUse', - user: game.user.id, - content: await foundry.applications.handlebars.renderTemplate( - 'systems/daggerheart/templates/ui/chat/ability-use.hbs', - systemData - ), - system: systemData - }); - - cls.create(msg.toObject()); - } - - static async selectClass() { - (await game.packs.get('daggerheart.classes'))?.render(true); - } - - static async selectSubclass() { - (await game.packs.get('daggerheart.subclasses'))?.render(true); - } - - static async selectCommunity() { - (await game.packs.get('daggerheart.communities'))?.render(true); + /** + * Trigger the context menu. + * @type {ApplicationClickAction} + */ + static #triggerContextMenu(event, _) { + return CONFIG.ux.ContextMenu.triggerContextMenu(event); } + /** + * Use a item + * @type {ApplicationClickAction} + */ static async useItem(event, button) { const item = this.getItem(button); if (!item) return; @@ -744,95 +594,10 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { } } - static async viewObject(event) { - const item = this.getItem(event); - if (!item) return; - - if (item.sheet) { - item.sheet.render(true); - } else { - await new DHActionConfig(item).render(true); - } - } - - editItem(event) { - const item = this.getItem(event); - if (!item) return; - - if (item.sheet.editMode) item.sheet.editMode = false; - - item.sheet.render(true); - } - - static async takeShortRest() { - await new DhpDowntime(this.document, true).render(true); - await this.minimize(); - } - - static async takeLongRest() { - await new DhpDowntime(this.document, false).render(true); - await this.minimize(); - } - - static async deleteScar(event, button) { - event.stopPropagation(); - await this.document.update({ - 'system.story.scars': this.document.system.story.scars.filter( - (_, index) => index !== Number.parseInt(button.currentTarget.dataset.scar) - ) - }); - } - - static async makeDeathMove() { - if (this.document.system.resources.hitPoints.value >= this.document.system.resources.hitPoints.maxTotal) { - await new DhpDeathMove(this.document).render(true); - } - } - - async onLevelChange(event) { - await this.document.updateLevel(Number(event.currentTarget.value)); - this.render(); - } - - static async deleteItem(event) { - const item = this.getItem(event); - if (!item) return; - - await item.delete(); - } - - static async setItemQuantity(button, value) { - const item = this.getItem(button); - if (!item) return; - await item.update({ 'system.quantity': Math.max(item.system.quantity + value, 1) }); - } - - static async useFeature(event, button) { - const item = this.getItem(event); - if (!item) return; - - const cls = getDocumentClass('ChatMessage'); - const systemData = { - title: game.i18n.localize('DAGGERHEART.UI.Chat.featureTitle'), - origin: this.document.id, - img: item.img, - name: item.name, - description: item.system.description, - actions: item.system.actions - }; - const msg = new cls({ - type: 'abilityUse', - user: game.user.id, - content: await foundry.applications.handlebars.renderTemplate( - 'systems/daggerheart/templates/ui/chat/ability-use.hbs', - systemData - ), - system: systemData - }); - - cls.create(msg.toObject()); - } - + /** + * Send item to Chat + * @type {ApplicationClickAction} + */ static async toChat(event, button) { if (button?.dataset?.type === 'experience') { const experience = this.document.system.experiences[button.dataset.uuid]; @@ -859,92 +624,6 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { } } - static async useAdvancementCard(_, button) { - const item = - button.dataset.multiclass === 'true' - ? this.document.system.multiclass.subclass - : this.document.system.class.subclass; - const ability = item.system[`${button.dataset.key}Feature`]; - const title = `${item.name} - ${game.i18n.localize( - `DAGGERHEART.ITEMS.DomainCard.${capitalize(button.dataset.key)}Title` - )}`; - - const cls = getDocumentClass('ChatMessage'); - const systemData = { - title: game.i18n.localize('DAGGERHEART.UI.Chat.foundationCard.subclassFeatureTitle'), - origin: this.document.id, - name: title, - img: item.img, - description: ability.description - }; - const msg = new cls({ - type: 'abilityUse', - user: game.user.id, - system: systemData, - content: await foundry.applications.handlebars.renderTemplate( - 'systems/daggerheart/templates/ui/chat/ability-use.hbs', - systemData - ) - }); - - cls.create(msg.toObject()); - } - - static async useAdvancementAbility(_, button) { - const item = this.document.items.find(x => x.uuid === button.dataset.id); - - const cls = getDocumentClass('ChatMessage'); - const systemData = { - title: game.i18n.localize('DAGGERHEART.UI.Chat.foundationCard.subclassFeatureTitle'), - origin: this.document.id, - name: item.name, - img: item.img, - description: item.system.description - }; - const msg = new cls({ - user: game.user.id, - system: systemData, - content: await foundry.applications.handlebars.renderTemplate( - 'systems/daggerheart/templates/ui/chat/ability-use.hbs', - systemData - ) - }); - - cls.create(msg.toObject()); - } - - static async toggleEquipItem(event, button) { - const item = this.getItem(event); - if (!item) return; - if (item.system.equipped) { - await item.update({ 'system.equipped': false }); - return; - } - - switch (item.type) { - case 'armor': - const currentArmor = this.document.system.armor; - if (currentArmor) { - await currentArmor.update({ 'system.equipped': false }); - } - - await item.update({ 'system.equipped': true }); - break; - case 'weapon': - await this.document.system.constructor.unequipBeforeEquip.bind(this.document.system)(item); - - await item.update({ 'system.equipped': true }); - break; - } - this.render(); - } - - static async toggleVault(event, button) { - const item = this.getItem(event); - if (!item) return; - await item.update({ 'system.inVault': !item.system.inVault }); - } - async _onDragStart(_, event) { super._onDragStart(event); } diff --git a/module/applications/sheets/actors/companion.mjs b/module/applications/sheets/actors/companion.mjs index e8a3ddaa..042a4bfa 100644 --- a/module/applications/sheets/actors/companion.mjs +++ b/module/applications/sheets/actors/companion.mjs @@ -1,22 +1,15 @@ -import DaggerheartSheet from '../daggerheart-sheet.mjs'; -import DHCompanionSettings from '../../sheets-configs/companion-settings.mjs'; +import DHBaseActorSheet from '../api/base-actor.mjs'; -const { ActorSheetV2 } = foundry.applications.sheets; -export default class DhCompanionSheet extends DaggerheartSheet(ActorSheetV2) { +/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ + +export default class DhCompanionSheet extends DHBaseActorSheet { static DEFAULT_OPTIONS = { - tag: 'form', - classes: ['daggerheart', 'sheet', 'actor', 'dh-style', 'companion'], + classes: ['actor', 'companion'], position: { width: 300 }, actions: { viewActor: this.viewActor, - openSettings: this.openSettings, useItem: this.useItem, toChat: this.toChat - }, - form: { - handler: this.updateForm, - submitOnChange: true, - closeOnSubmit: false } }; @@ -26,37 +19,20 @@ export default class DhCompanionSheet extends DaggerheartSheet(ActorSheetV2) { effects: { template: 'systems/daggerheart/templates/sheets/actors/companion/effects.hbs' } }; + /* -------------------------------------------- */ + + /** @inheritdoc */ static TABS = { - details: { - active: true, - cssClass: '', - group: 'primary', - id: 'details', - icon: null, - label: 'DAGGERHEART.GENERAL.Tabs.details' - }, - effects: { - active: false, - cssClass: '', - group: 'primary', - id: 'effects', - icon: null, - label: 'DAGGERHEART.GENERAL.Tabs.effects' + primary: { + tabs: [{ id: 'details' }, { id: 'effects' }], + initial: 'details', + labelPrefix: 'DAGGERHEART.GENERAL.Tabs' } }; - async _prepareContext(_options) { - const context = await super._prepareContext(_options); - context.document = this.document; - context.tabs = super._getTabs(this.constructor.TABS); - - return context; - } - - static async updateForm(event, _, formData) { - await this.document.update(formData.object); - this.render(); - } + /* -------------------------------------------- */ + /* Application Clicks Actions */ + /* -------------------------------------------- */ static async viewActor(_, button) { const target = button.closest('[data-item-uuid]'); @@ -101,8 +77,4 @@ export default class DhCompanionSheet extends DaggerheartSheet(ActorSheetV2) { item.toChat(this.document.id); } } - - static async openSettings() { - await new DHCompanionSettings(this.document).render(true); - } } diff --git a/module/applications/sheets/actors/environment.mjs b/module/applications/sheets/actors/environment.mjs index 9f3e8bb3..188d24b4 100644 --- a/module/applications/sheets/actors/environment.mjs +++ b/module/applications/sheets/actors/environment.mjs @@ -1,29 +1,22 @@ -import DaggerheartSheet from '../daggerheart-sheet.mjs'; -import DHEnvironmentSettings from '../../sheets-configs/environment-settings.mjs'; +import DHBaseActorSheet from '../api/base-actor.mjs'; -const { ActorSheetV2 } = foundry.applications.sheets; -export default class DhpEnvironment extends DaggerheartSheet(ActorSheetV2) { +/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ + +export default class DhpEnvironment extends DHBaseActorSheet { + /**@inheritdoc */ static DEFAULT_OPTIONS = { - tag: 'form', - classes: ['daggerheart', 'sheet', 'actor', 'dh-style', 'environment'], + classes: ['environment'], position: { width: 500 }, actions: { - addAdversary: this.addAdversary, - deleteProperty: this.deleteProperty, - openSettings: this.openSettings, useItem: this.useItem, toChat: this.toChat }, - form: { - handler: this._updateForm, - submitOnChange: true, - closeOnSubmit: false - }, dragDrop: [{ dragSelector: '.action-section .inventory-item', dropSelector: null }] }; + /**@override */ static PARTS = { header: { template: 'systems/daggerheart/templates/sheets/actors/environment/header.hbs' }, features: { template: 'systems/daggerheart/templates/sheets/actors/environment/features.hbs' }, @@ -33,41 +26,16 @@ export default class DhpEnvironment extends DaggerheartSheet(ActorSheetV2) { notes: { template: 'systems/daggerheart/templates/sheets/actors/environment/notes.hbs' } }; + /** @inheritdoc */ static TABS = { - features: { - active: true, - cssClass: '', - group: 'primary', - id: 'features', - icon: null, - label: 'DAGGERHEART.GENERAL.Tabs.features' - }, - potentialAdversaries: { - active: false, - cssClass: '', - group: 'primary', - id: 'potentialAdversaries', - icon: null, - label: 'DAGGERHEART.GENERAL.Tabs.potentialAdversaries' - }, - notes: { - active: false, - cssClass: '', - group: 'primary', - id: 'notes', - icon: null, - label: 'DAGGERHEART.GENERAL.Tabs.notes' + primary: { + tabs: [{ id: 'features' }, { id: 'potentialAdversaries' }, { id: 'notes' }], + initial: 'features', + labelPrefix: 'DAGGERHEART.GENERAL.Tabs' } }; - async _prepareContext(_options) { - const context = await super._prepareContext(_options); - context.document = this.document; - context.tabs = super._getTabs(this.constructor.TABS); - context.getEffectDetails = this.getEffectDetails.bind(this); - - return context; - } + /* -------------------------------------------- */ getItem(element) { const itemId = (element.target ?? element).closest('[data-item-id]').dataset.itemId, @@ -75,33 +43,14 @@ export default class DhpEnvironment extends DaggerheartSheet(ActorSheetV2) { return item; } - static async openSettings() { - await new DHEnvironmentSettings(this.document).render(true); - } - - static async _updateForm(event, _, formData) { - await this.document.update(formData.object); - this.render(); - } - - getEffectDetails(id) { - return {}; - } - - static async addAdversary() { - await this.document.update({ - [`system.potentialAdversaries.${foundry.utils.randomID()}.label`]: game.i18n.localize( - 'DAGGERHEART.ACTORS.Environment.newAdversary' - ) - }); - this.render(); - } - - static async deleteProperty(_, target) { - await this.document.update({ [`${target.dataset.path}.-=${target.id}`]: null }); - this.render(); - } + /* -------------------------------------------- */ + /* Application Clicks Actions */ + /* -------------------------------------------- */ + /** + * + * @type {ApplicationClickAction} + */ async viewAdversary(_, button) { const target = button.closest('[data-item-uuid]'); const adversary = await foundry.utils.fromUuid(target.dataset.itemUuid); @@ -110,7 +59,7 @@ export default class DhpEnvironment extends DaggerheartSheet(ActorSheetV2) { return; } - adversary.sheet.render(true); + adversary.sheet.render({ force: true }); } static async useItem(event, button) { diff --git a/module/applications/sheets/api/_modules.mjs b/module/applications/sheets/api/_modules.mjs index 4316da2a..cb7eee62 100644 --- a/module/applications/sheets/api/_modules.mjs +++ b/module/applications/sheets/api/_modules.mjs @@ -1,3 +1,5 @@ export { default as DHApplicationMixin } from './application-mixin.mjs'; export { default as DHBaseItemSheet } from './base-item.mjs'; export { default as DHHeritageSheet } from './heritage-sheet.mjs'; +export { default as DHBaseActorSheet } from './base-actor.mjs'; +export { default as DHBaseActorSettings } from './actor-setting.mjs'; diff --git a/module/applications/sheets/api/actor-setting.mjs b/module/applications/sheets/api/actor-setting.mjs new file mode 100644 index 00000000..50e2b0a9 --- /dev/null +++ b/module/applications/sheets/api/actor-setting.mjs @@ -0,0 +1,50 @@ +import DHApplicationMixin from './application-mixin.mjs'; +const { DocumentSheetV2 } = foundry.applications.api; + +/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ + +/** + * Base settings sheet for Daggerheart actors. + * @extends {DHApplicationMixin} + */ +export default class DHBaseActorSettings extends DHApplicationMixin(DocumentSheetV2) { + /**@inheritdoc */ + static DEFAULT_OPTIONS = { + classes: ['dialog'], + window: { + icon: 'fa-solid fa-wrench', + resizable: false, + title: 'DAGGERHEART.GENERAL.Tabs.settings' + }, + position: { width: 455, height: 'auto' }, + actions: {}, + form: { + submitOnChange: true + }, + dragDrop: [ + { dragSelector: null, dropSelector: '.tab.features' }, + { dragSelector: '.feature-item', dropSelector: null } + ] + }; + + /** @inheritDoc */ + _initializeApplicationOptions(options) { + options = super._initializeApplicationOptions(options); + options.classes = options.classes.filter(c => c !== 'sheet'); + return options; + } + + /**@returns {foundry.documents.Actor} */ + get actor() { + return this.document; + } + + /**@inheritdoc */ + async _prepareContext(options) { + const context = await super._prepareContext(options); + context.systemFields.attack.fields = this.actor.system.attack.schema.fields; + context.isNPC = this.actor.isNPC; + + return context; + } +} diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index 3ba471db..d0ef63d6 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -6,6 +6,14 @@ import { tagifyElement } from '../../../helpers/utils.mjs'; * @property {string} [dragSelector] - A CSS selector that identifies draggable elements. * @property {string} [dropSelector] - A CSS selector that identifies drop targets. * + * @typedef {object} ContextMenuConfig + * @property {() => ContextMenuEntry[]} handler - A handler function that provides initial context options + * @property {string} selector - A CSS selector to which the ContextMenu will be bound + * @property {object} [options] - Additional options which affect ContextMenu construction + * @property {HTMLElement} [options.container] - A parent HTMLElement which contains the selector target + * @property {string} [options.hookName] - The hook name + * @property {boolean} [options.parentClassHooks=true] - Whether to call hooks for the parent classes in the inheritance chain. + * * @typedef {Object} TagOption * @property {string} label * @property {string} [src] @@ -24,9 +32,17 @@ import { tagifyElement } from '../../../helpers/utils.mjs'; * * @typedef {Object} TagifyOptions * @property {number} [maxTags] - Maximum number of allowed tags - * + */ + +/** * @typedef {import("@client/applications/api/handlebars-application.mjs").HandlebarsRenderOptions} HandlebarsRenderOptions - * @typedef {foundry.applications.types.ApplicationConfiguration & HandlebarsRenderOptions & { dragDrop?: DragDropConfig[], tagifyConfigs?: TagifyConfig[] }} DHSheetV2Configuration + * @typedef {foundry.applications.types.ApplicationConfiguration} FoundryAppConfig + * + * @typedef {FoundryAppConfig & HandlebarsRenderOptions & { + * dragDrop?: DragDropConfig[], + * tagifyConfigs?: TagifyConfig[], + * contextMenus?: ContextMenuConfig[], + * }} DHSheetV2Configuration */ /** @@ -54,15 +70,12 @@ export default function DHApplicationMixin(Base) { */ static DEFAULT_OPTIONS = { classes: ['daggerheart', 'sheet', 'dh-style'], - position: { - width: 480, - height: 'auto' - }, actions: { - addEffect: DHSheetV2.#addEffect, - editEffect: DHSheetV2.#editEffect, - removeEffect: DHSheetV2.#removeEffect + createDoc: DHSheetV2.#createDoc, + editDoc: DHSheetV2.#editDoc, + deleteDoc: DHSheetV2.#deleteDoc }, + contextMenus: [], dragDrop: [], tagifyConfigs: [] }; @@ -74,6 +87,11 @@ export default function DHApplicationMixin(Base) { super._attachPartListeners(partId, htmlElement, options); this._dragDrop.forEach(d => d.bind(htmlElement)); } + /**@inheritdoc */ + async _onFirstRender(context, options) { + await super._onFirstRender(context, options); + if (!!this.options.contextMenus.length) this._createContextMenus(); + } /**@inheritdoc */ async _onRender(context, options) { @@ -81,6 +99,10 @@ export default function DHApplicationMixin(Base) { this._createTagifyElements(this.options.tagifyConfigs); } + /* -------------------------------------------- */ + /* Tags */ + /* -------------------------------------------- */ + /** * Creates Tagify elements from configuration objects * @param {TagifyConfig[]} tagConfigs - Array of Tagify configuration objects @@ -150,22 +172,38 @@ export default function DHApplicationMixin(Base) { _onDrop(event) {} /* -------------------------------------------- */ - /* Prepare Context */ + /* Context Menu */ + /* -------------------------------------------- */ + + _createContextMenus() { + for (const config of this.options.contextMenus) { + const { handler, selector, options } = config; + this._createContextMenu(handler.bind(this), selector, options); + } + } + /* -------------------------------------------- */ /** - * Prepare the template context. - * @param {object} options - * @param {string} [objectPath='document'] - * @returns {Promise} - * @inheritdoc + * Get the set of ContextMenu options which should be used for journal entry pages in the sidebar. + * @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} + * @protected */ - async _prepareContext(options, objectPath = 'document') { + _getEntryContextOptions() { + return []; + } + + /* -------------------------------------------- */ + /* Prepare Context */ + /* -------------------------------------------- */ + + /**@inheritdoc*/ + async _prepareContext(options) { const context = await super._prepareContext(options); context.config = CONFIG.DH; - context.source = this[objectPath]; - context.fields = this[objectPath].schema.fields; - context.systemFields = this[objectPath].system ? this[objectPath].system.schema.fields : {}; + context.source = this.document; + context.fields = this.document.schema.fields; + context.systemFields = this.document.system.schema.fields; return context; } @@ -174,37 +212,45 @@ export default function DHApplicationMixin(Base) { /* -------------------------------------------- */ /** - * Renders an ActiveEffect's sheet sheet. + * Create an embedded document. * @param {PointerEvent} event - The originating click event * @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"] */ - static async #addEffect() { - const cls = foundry.documents.ActiveEffect; - await cls.create( - { - name: game.i18n.format('DOCUMENT.New', { type: game.i18n.localize(cls.metadata.label) }) - }, - { parent: this.document } + static async #createDoc(event, button) { + const { documentClass, type } = button.dataset; + console.log(documentClass, type); + const parent = this.document; + + const cls = getDocumentClass(documentClass); + return await cls.createDocuments( + [ + { + name: cls.defaultName({ type, parent }), + type + } + ], + { parent, renderSheet: !event.shiftKey } ); } /** - * Renders an ActiveEffect's sheet sheet. + * Renders an embedded document. * @param {PointerEvent} event - The originating click event * @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"] */ - static async #editEffect(_event, button) { - const effect = this.document.effects.get(button.dataset.effect); - effect.sheet.render({ force: true }); + static #editDoc(_event, button) { + const { type, docId } = button.dataset; + this.document.getEmbeddedDocument(type, docId, { strict: true }).sheet.render({ force: true }); } /** - * Delete an ActiveEffect from the item. + * Delete an embedded document. * @param {PointerEvent} _event - The originating click event * @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"] */ - static async #removeEffect(_event, button) { - await this.document.effects.get(button.dataset.effect).delete(); + static async #deleteDoc(_event, button) { + const { type, docId } = button.dataset; + await this.document.getEmbeddedDocument(type, docId, { strict: true }).delete(); } } diff --git a/module/applications/sheets/api/base-actor.mjs b/module/applications/sheets/api/base-actor.mjs new file mode 100644 index 00000000..7102fa1c --- /dev/null +++ b/module/applications/sheets/api/base-actor.mjs @@ -0,0 +1,52 @@ +import DHBaseActorSettings from './actor-setting.mjs'; +import DHApplicationMixin from './application-mixin.mjs'; + +const { ActorSheetV2 } = foundry.applications.sheets; + +/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ + +/** + * A base actor sheet extending {@link ActorSheetV2} via {@link DHApplicationMixin} + * @extends ActorSheetV2 + * @mixes DHSheetV2 + */ +export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { + /** @inheritDoc */ + static DEFAULT_OPTIONS = { + classes: ['actor'], + position: { + width: 480 + }, + form: { + submitOnChange: true + }, + actions: { + openSettings: DHBaseActorSheet.#openSettings + }, + dragDrop: [] + }; + + /**@type {typeof DHBaseActorSettings}*/ + #settingSheet; + + /**@returns {DHBaseActorSettings|null} */ + get settingSheet() { + const SheetClass = this.document.system.metadata.settingSheet; + return (this.#settingSheet ??= SheetClass ? new SheetClass({ document: this.document }) : null); + } + + /**@inheritdoc */ + async _prepareContext(_options) { + const context = await super._prepareContext(_options); + context.isNPC = this.document.isNPC; + return context; + } + + /** + * Open the Actor Setting Sheet + * @type {ApplicationClickAction} + */ + static async #openSettings() { + await this.settingSheet.render({ force: true }); + } +} diff --git a/module/applications/sheets/api/base-item.mjs b/module/applications/sheets/api/base-item.mjs index e7d9b54b..f9d52025 100644 --- a/module/applications/sheets/api/base-item.mjs +++ b/module/applications/sheets/api/base-item.mjs @@ -3,6 +3,8 @@ import DHApplicationMixin from './application-mixin.mjs'; const { ItemSheetV2 } = foundry.applications.sheets; +/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ + /** * A base item sheet extending {@link ItemSheetV2} via {@link DHApplicationMixin} * @extends ItemSheetV2 @@ -92,8 +94,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { /** * Add a new action to the item, prompting the user for its type. - * @param {PointerEvent} _event - The originating click event - * @param {HTMLElement} _button - The capturing HTML element which defines the [data-action="addAction"] + * @type {ApplicationClickAction} */ static async #addAction(_event, _button) { const actionType = await DHBaseItemSheet.selectActionType(); @@ -124,8 +125,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { /** * Edit an existing action on the item - * @param {PointerEvent} _event - The originating click event - * @param {HTMLElement} button - The capturing HTML element which defines the [data-action="editAction"] + * @type {ApplicationClickAction} */ static async #editAction(_event, button) { const action = this.document.system.actions[button.dataset.index]; @@ -134,8 +134,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { /** * Remove an action from the item. - * @param {PointerEvent} event - The originating click event - * @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"] + * @type {ApplicationClickAction} */ static async #removeAction(event, button) { event.stopPropagation(); @@ -147,8 +146,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { /** * Add a new feature to the item, prompting the user for its type. - * @param {PointerEvent} _event - The originating click event - * @param {HTMLElement} _button - The capturing HTML element which defines the [data-action="addFeature"] + * @type {ApplicationClickAction} */ static async #addFeature(_event, _button) { const feature = await game.items.documentClass.create({ @@ -162,8 +160,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { /** * Edit an existing feature on the item - * @param {PointerEvent} _event - The originating click event - * @param {HTMLElement} button - The capturing HTML element which defines the [data-action="editFeature"] + * @type {ApplicationClickAction} */ static async #editFeature(_event, button) { const target = button.closest('.feature-item'); @@ -178,8 +175,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { /** * Remove a feature from the item. - * @param {PointerEvent} event - The originating click event - * @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeFeature"] + * @type {ApplicationClickAction} */ static async #removeFeature(event, button) { event.stopPropagation(); diff --git a/module/applications/sheets/daggerheart-sheet.mjs b/module/applications/sheets/daggerheart-sheet.mjs index aed1dccc..ddda25f7 100644 --- a/module/applications/sheets/daggerheart-sheet.mjs +++ b/module/applications/sheets/daggerheart-sheet.mjs @@ -1,5 +1,7 @@ const { HandlebarsApplicationMixin } = foundry.applications.api; +//Just used by action config + export default function DhpApplicationMixin(Base) { return class DhpSheetV2 extends HandlebarsApplicationMixin(Base) { constructor(options = {}) { diff --git a/module/data/actor/adversary.mjs b/module/data/actor/adversary.mjs index f2f31b6c..3cbf4eaa 100644 --- a/module/data/actor/adversary.mjs +++ b/module/data/actor/adversary.mjs @@ -1,3 +1,4 @@ +import DHAdversarySettings from '../../applications/sheets-configs/adversary-settings.mjs'; import ActionField from '../fields/actionField.mjs'; import BaseDataActor from './base.mjs'; @@ -13,7 +14,8 @@ export default class DhpAdversary extends BaseDataActor { static get metadata() { return foundry.utils.mergeObject(super.metadata, { label: 'TYPES.Actor.adversary', - type: 'adversary' + type: 'adversary', + settingSheet: DHAdversarySettings, }); } diff --git a/module/data/actor/base.mjs b/module/data/actor/base.mjs index 442c6e93..1b90968d 100644 --- a/module/data/actor/base.mjs +++ b/module/data/actor/base.mjs @@ -1,18 +1,29 @@ +import DHBaseActorSettings from "../../applications/sheets/api/actor-setting.mjs"; + /** * Describes metadata about the actor data model type * @typedef {Object} ActorDataModelMetadata * @property {string} label - A localizable label used on application. * @property {string} type - The system type that this data model represents. + * @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. */ export default class BaseDataActor extends foundry.abstract.TypeDataModel { /** @returns {ActorDataModelMetadata}*/ static get metadata() { return { label: 'Base Actor', - type: 'base' + type: 'base', + isNPC: true, + settingSheet: null, }; } + /**@returns {ActorDataModelMetadata}*/ + get metadata() { + return this.constructor.metadata; + } + /** @inheritDoc */ static defineSchema() { const fields = foundry.data.fields; diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 4ccda1cd..1dfbd015 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -28,7 +28,8 @@ export default class DhCharacter extends BaseDataActor { static get metadata() { return foundry.utils.mergeObject(super.metadata, { label: 'TYPES.Actor.character', - type: 'character' + type: 'character', + isNPC: false, }); } diff --git a/module/data/actor/companion.mjs b/module/data/actor/companion.mjs index 8745473f..1203cc96 100644 --- a/module/data/actor/companion.mjs +++ b/module/data/actor/companion.mjs @@ -3,6 +3,7 @@ import DhLevelData from '../levelData.mjs'; import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs'; import ActionField from '../fields/actionField.mjs'; import { adjustDice, adjustRange } from '../../helpers/utils.mjs'; +import DHCompanionSettings from '../../applications/sheets-configs/companion-settings.mjs'; export default class DhCompanion extends BaseDataActor { static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Companion']; @@ -10,7 +11,8 @@ export default class DhCompanion extends BaseDataActor { static get metadata() { return foundry.utils.mergeObject(super.metadata, { label: 'TYPES.Actor.companion', - type: 'companion' + type: 'companion', + settingSheet: DHCompanionSettings }); } diff --git a/module/data/actor/environment.mjs b/module/data/actor/environment.mjs index 2cd3338f..76990e88 100644 --- a/module/data/actor/environment.mjs +++ b/module/data/actor/environment.mjs @@ -1,5 +1,6 @@ import BaseDataActor from './base.mjs'; import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs'; +import DHEnvironmentSettings from '../../applications/sheets-configs/environment-settings.mjs'; export default class DhEnvironment extends BaseDataActor { static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Environment']; @@ -7,7 +8,8 @@ export default class DhEnvironment extends BaseDataActor { static get metadata() { return foundry.utils.mergeObject(super.metadata, { label: 'TYPES.Actor.environment', - type: 'environment' + type: 'environment', + settingSheet: DHEnvironmentSettings }); } diff --git a/module/data/item/base.mjs b/module/data/item/base.mjs index d0e05eb8..9358a6b3 100644 --- a/module/data/item/base.mjs +++ b/module/data/item/base.mjs @@ -22,6 +22,11 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel { }; } + /**@returns {ItemDataModelMetadata}*/ + get metadata() { + return this.constructor.metadata; + } + /** @inheritDoc */ static defineSchema() { const schema = {}; @@ -60,9 +65,9 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel { /**@inheritdoc */ async _preCreate(data, options, user) { // Skip if no initial action is required or actions already exist - if (!this.constructor.metadata.hasInitialAction || !foundry.utils.isEmpty(this.actions)) return; + if (!this.metadata.hasInitialAction || !foundry.utils.isEmpty(this.actions)) return; - const metadataType = this.constructor.metadata.type; + const metadataType = this.metadata.type; const actionType = { weapon: 'attack' }[metadataType]; const ActionClass = game.system.api.models.actions.actionsTypes[actionType]; diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 812f7963..b48d8c26 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -4,7 +4,16 @@ import DamageReductionDialog from '../applications/dialogs/damageReductionDialog import { LevelOptionType } from '../data/levelTier.mjs'; import DHFeature from '../data/item/feature.mjs'; -export default class DhpActor extends Actor { +export default class DhpActor extends foundry.documents.Actor { + + /** + * Whether this actor is an NPC. + * @returns {boolean} + */ + get isNPC() { + return this.system.metadata.isNPC; + } + async _preCreate(data, options, user) { if ((await super._preCreate(data, options, user)) === false) return false; @@ -351,16 +360,16 @@ export default class DhpActor extends Actor { const modifier = roll.modifier !== null ? Number.parseInt(roll.modifier) : null; return modifier !== null ? [ - { - value: modifier, - label: roll.label - ? modifier >= 0 - ? `${roll.label} +${modifier}` - : `${roll.label} ${modifier}` - : null, - title: roll.label - } - ] + { + value: modifier, + label: roll.label + ? modifier >= 0 + ? `${roll.label} +${modifier}` + : `${roll.label} ${modifier}` + : null, + title: roll.label + } + ] : []; } @@ -440,10 +449,10 @@ export default class DhpActor extends Actor { damage >= this.system.damageThresholds.severe ? 3 : damage >= this.system.damageThresholds.major - ? 2 - : damage >= this.system.damageThresholds.minor - ? 1 - : 0; + ? 2 + : damage >= this.system.damageThresholds.minor + ? 1 + : 0; if ( this.type === 'character' && diff --git a/module/documents/item.mjs b/module/documents/item.mjs index d3a22ad7..35b9e32f 100644 --- a/module/documents/item.mjs +++ b/module/documents/item.mjs @@ -34,7 +34,7 @@ export default class DHItem extends foundry.documents.Item { * @returns {boolean} Returns `true` if the item is an inventory item. */ get isInventoryItem() { - return this.system.constructor.metadata.isInventoryItem ?? false; + return this.system.metadata.isInventoryItem ?? false; } /** @inheritdoc */ @@ -53,17 +53,17 @@ export default class DHItem extends foundry.documents.Item { const isInventoryItem = CONFIG.Item.dataModels[type]?.metadata?.isInventoryItem; const group = isInventoryItem === true - ? 'Inventory Items' + ? 'Inventory Items' //TODO localize : isInventoryItem === false - ? 'Character Items' - : 'Other'; + ? 'Character Items' //TODO localize + : 'Other'; //TODO localize return { value: type, label, group }; } ); if (!documentTypes.length) { - throw new Error('No document types were permitted to be created.'); + throw new Error('No document types were permitted to be created.'); //TODO localize } const sortedTypes = documentTypes.sort((a, b) => a.label.localeCompare(b.label, game.i18n.lang)); diff --git a/templates/sheets-settings/adversary-settings/experiences.hbs b/templates/sheets-settings/adversary-settings/experiences.hbs index 26ac5d44..fb758a40 100644 --- a/templates/sheets-settings/adversary-settings/experiences.hbs +++ b/templates/sheets-settings/adversary-settings/experiences.hbs @@ -3,7 +3,7 @@ data-tab='{{tabs.experiences.id}}' data-group='{{tabs.experiences.group}}' > - diff --git a/templates/sheets-settings/adversary-settings/features.hbs b/templates/sheets-settings/adversary-settings/features.hbs index e0a1bfce..f232dae9 100644 --- a/templates/sheets-settings/adversary-settings/features.hbs +++ b/templates/sheets-settings/adversary-settings/features.hbs @@ -3,7 +3,7 @@ data-tab='{{tabs.features.id}}' data-group='{{tabs.features.group}}' > -
@@ -16,8 +16,8 @@ {{feature.name}}
- - + +
{{/each}} diff --git a/templates/sheets-settings/companion-settings/details.hbs b/templates/sheets-settings/companion-settings/details.hbs index 802db637..2811377d 100644 --- a/templates/sheets-settings/companion-settings/details.hbs +++ b/templates/sheets-settings/companion-settings/details.hbs @@ -13,11 +13,11 @@
- {{selectOptions playerCharacters selected=document.system.partner.uuid labelAttr="name" valueAttr="key" blank=""}}
- + \ No newline at end of file diff --git a/templates/sheets-settings/environment-settings/adversaries.hbs b/templates/sheets-settings/environment-settings/adversaries.hbs index 8d75f943..4d8fa066 100644 --- a/templates/sheets-settings/environment-settings/adversaries.hbs +++ b/templates/sheets-settings/environment-settings/adversaries.hbs @@ -3,18 +3,20 @@ data-tab='{{tabs.adversaries.id}}' data-group='{{tabs.adversaries.group}}' > - - {{#each document.system.potentialAdversaries}} -
- {{this.label}} + {{#each document.system.potentialAdversaries as |category id|}} +
+ {{category.label}}
- - + + + +
- {{#each this.adversaries as |adversary|}} + {{#each category.adversaries as |adversary|}}
{{> 'systems/daggerheart/templates/sheets/global/partials/inventory-item.hbs' item=adversary type='adversary' isActor=true categoryAdversary=@../key}}
diff --git a/templates/sheets-settings/environment-settings/features.hbs b/templates/sheets-settings/environment-settings/features.hbs index e0a1bfce..f232dae9 100644 --- a/templates/sheets-settings/environment-settings/features.hbs +++ b/templates/sheets-settings/environment-settings/features.hbs @@ -3,7 +3,7 @@ data-tab='{{tabs.features.id}}' data-group='{{tabs.features.group}}' > -
@@ -16,8 +16,8 @@ {{feature.name}}
- - + +
{{/each}} diff --git a/templates/sheets/actors/character/header.hbs b/templates/sheets/actors/character/header.hbs index d5bfbcf9..2b4ae13c 100644 --- a/templates/sheets/actors/character/header.hbs +++ b/templates/sheets/actors/character/header.hbs @@ -13,7 +13,8 @@

{{#if (or document.system.needsCharacterSetup document.system.levelData.canLevelUp)}} -

- {{#if this.abilities.loadout.listView}} - {{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' title=(localize 'DAGGERHEART.GENERAL.Tabs.loadout') type='domainCard' isGlassy=true cardView='list'}} - {{else}} - {{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' title=(localize 'DAGGERHEART.GENERAL.Tabs.loadout') type='domainCard' isGlassy=true cardView='card'}} - {{/if}} + {{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' + title=(localize 'DAGGERHEART.GENERAL.Tabs.loadout') + type='domainCard' + isGlassy=true + cardView=(ifThen listView "list" "card")}} - {{#if this.abilities.loadout.listView}} - {{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' title=(localize 'DAGGERHEART.GENERAL.Tabs.vault') type='domainCard' isVault=true isGlassy=true cardView='list'}} - {{else}} - {{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' title=(localize 'DAGGERHEART.GENERAL.Tabs.vault') type='domainCard' isVault=true isGlassy=true cardView='card'}} - {{/if}} - - + {{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' + title=(localize 'DAGGERHEART.GENERAL.Tabs.vault') + type='domainCard' + isVault=true + isGlassy=true + cardView=(ifThen listView "list" "card")}}
\ No newline at end of file diff --git a/templates/sheets/actors/character/sidebar.hbs b/templates/sheets/actors/character/sidebar.hbs index f1e2b345..df275472 100644 --- a/templates/sheets/actors/character/sidebar.hbs +++ b/templates/sheets/actors/character/sidebar.hbs @@ -1,7 +1,7 @@