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 <joaquinpereyra98@users.noreply.github.com>
This commit is contained in:
joaquinpereyra98 2025-07-07 20:27:21 -03:00 committed by GitHub
parent 7d7fb88035
commit 87b3677956
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 723 additions and 1253 deletions

View file

@ -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) { /**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
constructor(actor) {
super({});
this.actor = actor;
this._dragDrop = this._createDragDropHandlers();
}
get title() {
return `${game.i18n.localize('DAGGERHEART.GENERAL.Tabs.settings')}`;
}
export default class DHAdversarySettings extends DHBaseActorSettings {
/**@inheritdoc */
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
tag: 'form', classes: ['adversary-settings'],
classes: ['daggerheart', 'dh-style', 'dialog', 'adversary-settings'],
window: {
icon: 'fa-solid fa-wrench',
resizable: false
},
position: { width: 455, height: 'auto' }, position: { width: 455, height: 'auto' },
actions: { actions: {
addExperience: this.#addExperience, addExperience: DHAdversarySettings.#addExperience,
removeExperience: this.#removeExperience, removeExperience: DHAdversarySettings.#removeExperience
addFeature: this.#addFeature,
editFeature: this.#editFeature,
removeFeature: this.#removeFeature
},
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
}, },
dragDrop: [ dragDrop: [
{ dragSelector: null, dropSelector: '.tab.features' }, { dragSelector: null, dropSelector: '.tab.features' },
@ -38,6 +17,7 @@ export default class DHAdversarySettings extends HandlebarsApplicationMixin(Appl
] ]
}; };
/**@override */
static PARTS = { static PARTS = {
header: { header: {
id: 'header', id: 'header',
@ -62,116 +42,35 @@ export default class DHAdversarySettings extends HandlebarsApplicationMixin(Appl
} }
}; };
/** @override */
static TABS = { static TABS = {
details: { primary: {
active: true, tabs: [{ id: 'details' }, { id: 'attack' }, { id: 'experiences' }, { id: 'features' }],
cssClass: '', initial: 'details',
group: 'primary', labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
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'
} }
}; };
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() { static async #addExperience() {
const newExperience = { const newExperience = {
name: 'Experience', name: 'Experience',
modifier: 0 modifier: 0
}; };
await this.actor.update({ [`system.experiences.${foundry.utils.randomID()}`]: newExperience }); 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) { static async #removeExperience(_, target) {
await this.actor.update({ [`system.experiences.-=${target.dataset.experience}`]: null }); 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) { async _onDragStart(event) {
@ -192,7 +91,6 @@ export default class DHAdversarySettings extends HandlebarsApplicationMixin(Appl
const item = await fromUuid(data.uuid); const item = await fromUuid(data.uuid);
if (item.type === 'feature') { if (item.type === 'feature') {
await this.actor.createEmbeddedDocuments('Item', [item]); await this.actor.createEmbeddedDocuments('Item', [item]);
this.render();
} }
} }
} }

View file

@ -1,37 +1,20 @@
import { GMUpdateEvent, socketEvent } from '../../systemRegistration/socket.mjs'; import { GMUpdateEvent, socketEvent } from '../../systemRegistration/socket.mjs';
import DhCompanionlevelUp from '../levelup/companionLevelup.mjs'; import DhCompanionlevelUp from '../levelup/companionLevelup.mjs';
import DHBaseActorSettings from '../sheets/api/actor-setting.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; /**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
export default class DHCompanionSettings extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(actor) {
super({});
this.actor = actor;
}
get title() {
return `${game.i18n.localize('DAGGERHEART.GENERAL.Tabs.settings')}`;
}
export default class DHCompanionSettings extends DHBaseActorSettings {
/**@inheritdoc */
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
tag: 'form', classes: ['companion-settings'],
classes: ['daggerheart', 'dh-style', 'dialog', 'companion-settings'],
window: {
icon: 'fa-solid fa-wrench',
resizable: false
},
position: { width: 455, height: 'auto' }, position: { width: 455, height: 'auto' },
actions: { actions: {
levelUp: this.levelUp levelUp: DHCompanionSettings.#levelUp
},
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
} }
}; };
/**@inheritdoc */
static PARTS = { static PARTS = {
header: { header: {
id: 'header', id: 'header',
@ -52,109 +35,64 @@ export default class DHCompanionSettings extends HandlebarsApplicationMixin(Appl
} }
}; };
/** @inheritdoc */
static TABS = { static TABS = {
details: { primary: {
active: true, tabs: [{ id: 'details' }, { id: 'attack' }, { id: 'experiences' }],
cssClass: '', initial: 'details',
group: 'primary', labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
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'
} }
}; };
_attachPartListeners(partId, htmlElement, options) { /**@inheritdoc */
super._attachPartListeners(partId, htmlElement, options); async _onRender(context, options) {
await super._onRender(context, options);
htmlElement.querySelector('.partner-value')?.addEventListener('change', this.onPartnerChange.bind(this)); this.element.querySelector('.partner-value')?.addEventListener('change', this.onPartnerChange.bind(this));
} }
/**@inheritdoc */
async _prepareContext(_options) { async _prepareContext(_options) {
const context = await super._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 context.playerCharacters = game.actors
.filter( .filter(x => x.type === 'character' && (x.isOwner || this.document.system.partner?.uuid === x.uuid))
x =>
x.type === 'character' &&
(x.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER) ||
this.document.system.partner?.uuid === x.uuid)
)
.map(x => ({ key: x.uuid, name: x.name })); .map(x => ({ key: x.uuid, name: x.name }));
return context; return context;
} }
_getTabs(tabs) { /**
for (const v of Object.values(tabs)) { * Handles changes to the actor's partner selection.
v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active; * @param {Event} event - The change event triggered by the partner input element.
v.cssClass = v.active ? 'active' : ''; */
}
return tabs;
}
async onPartnerChange(event) { async onPartnerChange(event) {
const partnerDocument = event.target.value const value = event.target.value;
? await foundry.utils.fromUuid(event.target.value) const partnerDocument = value ? await foundry.utils.fromUuid(value) : this.actor.system.partner;
: this.actor.system.partner; const partnerUpdate = { 'system.companion': value ? this.actor.uuid : null };
const partnerUpdate = { 'system.companion': event.target.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}`, { await game.socket.emit(`system.${CONFIG.DH.id}`, {
action: socketEvent.GMUpdate, action: socketEvent.GMUpdate,
data: { data: {
action: GMUpdateEvent.UpdateDocument, action: GMUpdateEvent.UpdateDocument,
uuid: partnerDocument.uuid, uuid: partnerDocument.uuid,
update: update update: partnerUpdate
} }
}); });
} else { } else {
await partnerDocument.update(partnerUpdate); await partnerDocument.update(partnerUpdate);
} }
await this.actor.update({ 'system.partner': event.target.value }); await this.actor.update({ 'system.partner': value });
if (!event.target.value) { if (!value) await this.actor.updateLevel(1);
await this.actor.updateLevel(1);
}
this.render();
} }
async viewActor(_, button) { /**
const target = button.closest('[data-item-uuid]'); * Opens the companion level-up dialog for the associated actor.
const actor = await foundry.utils.fromUuid(target.dataset.itemUuid); * @type {ApplicationClickAction}
if (!actor) return; */
static async #levelUp() {
actor.sheet.render(true); new DhCompanionlevelUp(this.actor).render({ force: true });
}
static async levelUp() {
new DhCompanionlevelUp(this.actor).render(true);
}
static async updateForm(event, _, formData) {
await this.actor.update(formData.object);
this.render();
} }
} }

View file

@ -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) { /**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
constructor(actor) {
super({});
this.actor = actor;
this._dragDrop = this._createDragDropHandlers();
}
get title() {
return `${game.i18n.localize('DAGGERHEART.GENERAL.Tabs.settings')}`;
}
export default class DHEnvironmentSettings extends DHBaseActorSettings {
/**@inheritdoc */
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
tag: 'form', classes: ['environment-settings'],
classes: ['daggerheart', 'dh-style', 'dialog', 'environment-settings'],
window: {
icon: 'fa-solid fa-wrench',
resizable: false
},
position: { width: 455, height: 'auto' },
actions: { actions: {
addFeature: this.#addFeature, addCategory: DHEnvironmentSettings.#addCategory,
editFeature: this.#editFeature, removeCategory: DHEnvironmentSettings.#removeCategory,
removeFeature: this.#removeFeature,
addCategory: this.#addCategory,
deleteProperty: this.#deleteProperty,
viewAdversary: this.#viewAdversary, viewAdversary: this.#viewAdversary,
deleteAdversary: this.#deleteAdversary deleteAdversary: this.#deleteAdversary
}, },
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
},
dragDrop: [ dragDrop: [
{ dragSelector: null, dropSelector: '.category-container' }, { dragSelector: null, dropSelector: '.category-container' },
{ dragSelector: null, dropSelector: '.tab.features' }, { dragSelector: null, dropSelector: '.tab.features' },
@ -41,6 +19,7 @@ export default class DHEnvironmentSettings extends HandlebarsApplicationMixin(Ap
] ]
}; };
/**@override */
static PARTS = { static PARTS = {
header: { header: {
id: 'header', id: 'header',
@ -61,100 +40,33 @@ export default class DHEnvironmentSettings extends HandlebarsApplicationMixin(Ap
} }
}; };
/** @inheritdoc */
static TABS = { static TABS = {
details: { primary: {
active: true, tabs: [{ id: 'details' }, { id: 'features' }, { id: 'adversaries' }],
cssClass: '', initial: 'details',
group: 'primary', labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
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'
} }
}; };
async _prepareContext(_options) { /**
const context = await super._prepareContext(_options); * Adds a new category entry to the actor.
context.document = this.actor; * @type {ApplicationClickAction}
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();
}
static async #addCategory() { static async #addCategory() {
await this.actor.update({ await this.actor.update({
[`system.potentialAdversaries.${foundry.utils.randomID()}.label`]: game.i18n.localize( [`system.potentialAdversaries.${foundry.utils.randomID()}.label`]: game.i18n.localize(
'DAGGERHEART.ACTORS.Environment.newAdversary' 'DAGGERHEART.ACTORS.Environment.newAdversary'
) )
}); });
this.render();
} }
static async #deleteProperty(_, target) { /**
await this.actor.update({ [`${target.dataset.path}.-=${target.id}`]: null }); * Removes an category entry from the actor.
this.render(); * @type {ApplicationClickAction}
*/
static async #removeCategory(_, target) {
await this.actor.update({ [`system.potentialAdversaries.-=${target.dataset.categoryId}`]: null });
} }
static async #viewAdversary(_, button) { static async #viewAdversary(_, button) {
@ -164,17 +76,17 @@ export default class DHEnvironmentSettings extends HandlebarsApplicationMixin(Ap
return; return;
} }
adversary.sheet.render(true); adversary.sheet.render({ force: true });
} }
static async #deleteAdversary(event, target) { static async #deleteAdversary(event, target) {
const adversaryKey = target.dataset.adversary; const adversaryKey = target.dataset.adversary;
const path = `system.potentialAdversaries.${target.dataset.potentialAdversary}.adversaries`; const path = `system.potentialAdversaries.${target.dataset.potentialAdversary}.adversaries`;
console.log(target.dataset.potentialAdversar);
const newAdversaries = foundry.utils const newAdversaries = foundry.utils
.getProperty(this.actor, path) .getProperty(this.actor, path)
.filter(x => x && (x?.uuid ?? x) !== adversaryKey); .filter(x => x && (x?.uuid ?? x) !== adversaryKey);
await this.actor.update({ [path]: newAdversaries }); await this.actor.update({ [path]: newAdversaries });
this.render();
} }
async _onDragStart(event) { async _onDragStart(event) {
@ -206,9 +118,4 @@ export default class DHEnvironmentSettings extends HandlebarsApplicationMixin(Ap
this.render(); this.render();
} }
} }
static async updateForm(event, _, formData) {
await this.actor.update(formData.object);
this.render();
}
} }

View file

@ -1,29 +1,19 @@
import DHActionConfig from '../../sheets-configs/action-config.mjs'; import DHBaseActorSheet from '../api/base-actor.mjs';
import DaggerheartSheet from '../daggerheart-sheet.mjs';
import DHAdversarySettings from '../../sheets-configs/adversary-settings.mjs';
const { ActorSheetV2 } = foundry.applications.sheets; /**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) {
export default class AdversarySheet extends DHBaseActorSheet {
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
tag: 'form', classes: ['adversary'],
classes: ['daggerheart', 'sheet', 'actor', 'dh-style', 'adversary'],
position: { width: 660, height: 766 }, position: { width: 660, height: 766 },
window: { resizable: true }, window: { resizable: true },
actions: { actions: {
reactionRoll: this.reactionRoll, reactionRoll: AdversarySheet.#reactionRoll,
useItem: this.useItem, useItem: this.useItem,
toChat: this.toChat, toChat: this.toChat
attackConfigure: this.attackConfigure,
addExperience: this.addExperience,
removeExperience: this.removeExperience,
toggleHP: this.toggleHP,
toggleStress: this.toggleStress,
openSettings: this.openSettings
}, },
form: { window: {
handler: this.updateForm, resizable: true
submitOnChange: true,
closeOnSubmit: false
} }
}; };
@ -35,40 +25,19 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) {
effects: { template: 'systems/daggerheart/templates/sheets/actors/adversary/effects.hbs' } effects: { template: 'systems/daggerheart/templates/sheets/actors/adversary/effects.hbs' }
}; };
/** @inheritdoc */
static TABS = { static TABS = {
features: { primary: {
active: true, tabs: [{ id: 'features' }, { id: 'notes' }, { id: 'effects' }],
cssClass: '', initial: 'features',
group: 'primary', labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
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'
} }
}; };
async _prepareContext(_options) { /**@inheritdoc */
const context = await super._prepareContext(_options); async _prepareContext(options) {
context.document = this.document; const context = await super._prepareContext(options);
context.tabs = super._getTabs(this.constructor.TABS);
context.systemFields.attack.fields = this.document.system.attack.schema.fields; context.systemFields.attack.fields = this.document.system.attack.schema.fields;
context.getEffectDetails = this.getEffectDetails.bind(this);
context.isNPC = true;
return context; return context;
} }
@ -78,18 +47,20 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) {
return item; return item;
} }
static async updateForm(event, _, formData) { /* -------------------------------------------- */
await this.document.update(formData.object); /* Application Clicks Actions */
this.render(); /* -------------------------------------------- */
}
static async reactionRoll(event) { /**
* Performs a reaction roll for an Adversary.
* @type {ApplicationClickAction}
*/
static #reactionRoll(event) {
const config = { const config = {
event: event, event: event,
title: `Reaction Roll: ${this.actor.name}`, title: `Reaction Roll: ${this.actor.name}`,
headerTitle: 'Adversary Reaction Roll', headerTitle: 'Adversary Reaction Roll',
roll: { roll: {
// modifier: null,
type: 'reaction' type: 'reaction'
}, },
chatMessage: { chatMessage: {
@ -98,22 +69,23 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) {
mute: true mute: true
} }
}; };
this.actor.diceRoll(config); this.actor.diceRoll(config);
} }
getEffectDetails(id) { /**
return {}; *
} * @type {ApplicationClickAction}
*/
static async openSettings() {
await new DHAdversarySettings(this.document).render(true);
}
static async useItem(event) { static async useItem(event) {
const action = this.getItem(event) ?? this.actor.system.attack; const action = this.getItem(event) ?? this.actor.system.attack;
action.use(event); action.use(event);
} }
/**
*
* @type {ApplicationClickAction}
*/
static async toChat(event, button) { static async toChat(event, button) {
if (button?.dataset?.type === 'experience') { if (button?.dataset?.type === 'experience') {
const experience = this.document.system.experiences[button.dataset.uuid]; 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); 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 });
}
} }

View file

@ -1,66 +1,48 @@
import { capitalize } from '../../../helpers/utils.mjs'; import DHBaseActorSheet from '../api/base-actor.mjs';
import DhpDeathMove from '../../dialogs/deathMove.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 { abilities } from '../../../config/actorConfig.mjs';
import DhCharacterlevelUp from '../../levelup/characterLevelup.mjs'; import DhCharacterlevelUp from '../../levelup/characterLevelup.mjs';
import DhCharacterCreation from '../../characterCreation/characterCreation.mjs'; import DhCharacterCreation from '../../characterCreation/characterCreation.mjs';
import FilterMenu from '../../ux/filter-menu.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; const { TextEditor } = foundry.applications.ux;
export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { export default class CharacterSheet extends DHBaseActorSheet {
constructor(options = {}) { /**@inheritdoc */
super(options);
}
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
tag: 'form', classes: ['character'],
classes: ['daggerheart', 'sheet', 'actor', 'dh-style', 'daggerheart', 'character'],
position: { width: 850, height: 800 }, position: { width: 850, height: 800 },
actions: { actions: {
attributeRoll: this.rollAttribute, triggerContextMenu: CharacterSheet.#triggerContextMenu,
toggleMarks: this.toggleMarks, toggleVault: CharacterSheet.#toggleVault,
toggleHP: this.toggleHP, rollAttribute: CharacterSheet.#rollAttribute,
toggleStress: this.toggleStress, toggleHope: CharacterSheet.#toggleHope,
toggleHope: this.toggleHope, toggleLoadoutView: CharacterSheet.#toggleLoadoutView,
toggleGold: this.toggleGold, openPack: CharacterSheet.#openPack,
toggleLoadoutView: this.toggleLoadoutView, makeDeathMove: CharacterSheet.#makeDeathMove,
attackRoll: this.attackRoll, levelManagement: CharacterSheet.#levelManagement,
useDomainCard: this.useDomainCard, toggleEquipItem: CharacterSheet.#toggleEquipItem,
selectClass: this.selectClass, useItem: this.useItem, //TODO Fix this
selectSubclass: this.selectSubclass, toChat: this.toChat
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
}, },
window: { window: {
resizable: true resizable: true
}, },
form: { dragDrop: [],
submitOnChange: true, contextMenus: [
closeOnSubmit: false {
}, handler: CharacterSheet._getContextMenuOptions,
dragDrop: [] selector: '[data-item-id]',
options: {
parentClassHooks: false,
fixed: true
}
}
]
}; };
/**@override */
static PARTS = { static PARTS = {
sidebar: { sidebar: {
id: 'sidebar', id: 'sidebar',
@ -92,217 +74,31 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
} }
}; };
/* -------------------------------------------- */
/** @inheritdoc */
static TABS = { static TABS = {
features: { primary: {
active: true, tabs: [{ id: 'features' }, { id: 'loadout' }, { id: 'inventory' }, { id: 'biography' }, { id: 'effects' }],
cssClass: '', initial: 'features',
group: 'primary', labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
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'
} }
}; };
_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 */ /** @inheritDoc */
async _onRender(context, options) { async _onRender(context, options) {
await super._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._createFilterMenus();
this._createSearchFilter(); this._createSearchFilter();
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
_createContextMenues() {
const allOptions = {
useItem: {
name: 'DAGGERHEART.GENERAL.use',
icon: '<i class="fa-solid fa-burst"></i>',
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: '<i class="fa-solid fa-hands"></i>',
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: '<i class="fa-solid fa-hands"></i>',
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: '<i class="fa-solid fa-arrow-up"></i>',
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: '<i class="fa-solid fa-arrow-down"></i>',
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: '<i class="fa-regular fa-message"></i>',
callback: this.constructor.toChat.bind(this)
},
edit: {
name: 'CONTROLS.CommonEdit',
icon: '<i class="fa-solid fa-pen-to-square"></i>',
callback: this.constructor.viewObject.bind(this)
},
delete: {
name: 'CONTROLS.CommonDelete',
icon: '<i class="fa-solid fa-trash"></i>',
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) { getItem(element) {
const listElement = (element.target ?? element).closest('[data-item-id]'); const listElement = (element.target ?? element).closest('[data-item-id]');
const itemId = listElement.dataset.itemId; 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); /* Prepare Context */
} /* -------------------------------------------- */
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();
}
async _prepareContext(_options) { async _prepareContext(_options) {
const context = await super._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) => { context.attributes = Object.keys(this.document.system.traits).reduce((acc, key) => {
acc[key] = { acc[key] = {
@ -369,6 +150,109 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
return context; 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: '<i class="fa-solid fa-burst"></i>',
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: '<i class="fa-solid fa-hands"></i>',
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: '<i class="fa-solid fa-hands"></i>',
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: '<i class="fa-solid fa-arrow-up"></i>',
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: '<i class="fa-solid fa-arrow-down"></i>',
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: '<i class="fa-regular fa-message"></i>',
callback: CharacterSheet.toChat.bind(this)
},
{
name: 'DAGGERHEART.Sheets.PC.ContextMenu.Edit',
icon: '<i class="fa-solid fa-pen-to-square"></i>',
callback: target => getItem(target).sheet.render({ force: true })
},
{
name: 'DAGGERHEART.Sheets.PC.ContextMenu.Delete',
icon: '<i class="fa-solid fa-trash"></i>',
callback: el => getItem(el).delete()
}
];
}
/* -------------------------------------------- */ /* -------------------------------------------- */
/* Filter Tracking */ /* Filter Tracking */
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -488,22 +372,7 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
li.hidden = !(menu.has(item.id) && matchesSearch); 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 */ /* Filter Menus */
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -579,33 +448,56 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
} }
} }
/* -------------------------------------------- */
/* Application Clicks Actions */
/* -------------------------------------------- */ /* -------------------------------------------- */
async mapFeatureType(data, configType) { /**
return await Promise.all( * Opens the character level management window.
data.map(async x => { * If the character requires setup, opens the character creation interface.
const abilities = x.system.abilities * If class or subclass is missing, shows an error notification.
? await Promise.all(x.system.abilities.map(async x => await fromUuid(x.uuid))) * @type {ApplicationClickAction}
: []; */
static #levelManagement() {
if (this.document.system.needsCharacterSetup)
return new DhCharacterCreation(this.document).render({ force: true });
return { const { value, subclass } = this.document.system.class;
...x, if (!value || !subclass)
uuid: x.uuid, return ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.missingClassOrSubclass'));
system: {
...x.system, new DhCharacterlevelUp(this.document).render({ force: true });
abilities: abilities,
type: game.i18n.localize(configType[x.system.type ?? x.type].label)
}
};
})
);
} }
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 abilityLabel = game.i18n.localize(abilities[button.dataset.attribute].label);
const config = { const config = {
event: event, 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: { roll: {
trait: button.dataset.attribute trait: button.dataset.attribute
} }
@ -613,120 +505,78 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
this.document.diceRoll(config); this.document.diceRoll(config);
} }
static async toggleMarks(_, button) { /**
const markValue = Number.parseInt(button.dataset.value); * Toggles the equipped state of an item (armor or weapon).
const newValue = this.document.system.armor.system.marks.value >= markValue ? markValue - 1 : markValue; * @type {ApplicationClickAction}
await this.document.system.armor.update({ 'system.marks.value': newValue }); */
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); * Toggles the current view of the character's loadout display.
const newValue = this.document.system.resources.hitPoints.value >= healthValue ? healthValue - 1 : healthValue; * @type {ApplicationClickAction}
await this.document.update({ 'system.resources.hitPoints.value': newValue }); */
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); * Toggles a hope resource value.
const newValue = this.document.system.resources.stress.value >= healthValue ? healthValue - 1 : healthValue; * @type {ApplicationClickAction}
await this.document.update({ 'system.resources.stress.value': newValue }); */
} static async #toggleHope(_, button) {
static async toggleHope(_, button) {
const hopeValue = Number.parseInt(button.dataset.value); const hopeValue = Number.parseInt(button.dataset.value);
const newValue = this.document.system.resources.hope.value >= hopeValue ? hopeValue - 1 : hopeValue; const newValue = this.document.system.resources.hope.value >= hopeValue ? hopeValue - 1 : hopeValue;
await this.document.update({ 'system.resources.hope.value': newValue }); await this.document.update({ 'system.resources.hope.value': newValue });
} }
static async toggleGold(_, button) { /**
const goldValue = Number.parseInt(button.dataset.value); * Toggles whether an item is stored in the vault.
const goldType = button.dataset.type; * @type {ApplicationClickAction}
const newValue = this.document.system.gold[goldType] >= goldValue ? goldValue - 1 : goldValue; */
static async #toggleVault(event, button) {
const update = `system.gold.${goldType}`; const docId = button.closest('[data-item-id]')?.dataset.itemId;
await this.document.update({ [update]: newValue }); 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'); * Trigger the context menu.
await game.user.setFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.displayDomainCardsAsList, newAbilityView); * @type {ApplicationClickAction}
this.render(); */
} static #triggerContextMenu(event, _) {
return CONFIG.ux.ContextMenu.triggerContextMenu(event);
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);
} }
/**
* Use a item
* @type {ApplicationClickAction}
*/
static async useItem(event, button) { static async useItem(event, button) {
const item = this.getItem(button); const item = this.getItem(button);
if (!item) return; if (!item) return;
@ -744,95 +594,10 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
} }
} }
static async viewObject(event) { /**
const item = this.getItem(event); * Send item to Chat
if (!item) return; * @type {ApplicationClickAction}
*/
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());
}
static async toChat(event, button) { static async toChat(event, button) {
if (button?.dataset?.type === 'experience') { if (button?.dataset?.type === 'experience') {
const experience = this.document.system.experiences[button.dataset.uuid]; 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) { async _onDragStart(_, event) {
super._onDragStart(event); super._onDragStart(event);
} }

View file

@ -1,22 +1,15 @@
import DaggerheartSheet from '../daggerheart-sheet.mjs'; import DHBaseActorSheet from '../api/base-actor.mjs';
import DHCompanionSettings from '../../sheets-configs/companion-settings.mjs';
const { ActorSheetV2 } = foundry.applications.sheets; /**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
export default class DhCompanionSheet extends DaggerheartSheet(ActorSheetV2) {
export default class DhCompanionSheet extends DHBaseActorSheet {
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
tag: 'form', classes: ['actor', 'companion'],
classes: ['daggerheart', 'sheet', 'actor', 'dh-style', 'companion'],
position: { width: 300 }, position: { width: 300 },
actions: { actions: {
viewActor: this.viewActor, viewActor: this.viewActor,
openSettings: this.openSettings,
useItem: this.useItem, useItem: this.useItem,
toChat: this.toChat 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' } effects: { template: 'systems/daggerheart/templates/sheets/actors/companion/effects.hbs' }
}; };
/* -------------------------------------------- */
/** @inheritdoc */
static TABS = { static TABS = {
details: { primary: {
active: true, tabs: [{ id: 'details' }, { id: 'effects' }],
cssClass: '', initial: 'details',
group: 'primary', labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
id: 'details',
icon: null,
label: 'DAGGERHEART.GENERAL.Tabs.details'
},
effects: {
active: false,
cssClass: '',
group: 'primary',
id: 'effects',
icon: null,
label: 'DAGGERHEART.GENERAL.Tabs.effects'
} }
}; };
async _prepareContext(_options) { /* -------------------------------------------- */
const context = await super._prepareContext(_options); /* Application Clicks Actions */
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();
}
static async viewActor(_, button) { static async viewActor(_, button) {
const target = button.closest('[data-item-uuid]'); const target = button.closest('[data-item-uuid]');
@ -101,8 +77,4 @@ export default class DhCompanionSheet extends DaggerheartSheet(ActorSheetV2) {
item.toChat(this.document.id); item.toChat(this.document.id);
} }
} }
static async openSettings() {
await new DHCompanionSettings(this.document).render(true);
}
} }

View file

@ -1,29 +1,22 @@
import DaggerheartSheet from '../daggerheart-sheet.mjs'; import DHBaseActorSheet from '../api/base-actor.mjs';
import DHEnvironmentSettings from '../../sheets-configs/environment-settings.mjs';
const { ActorSheetV2 } = foundry.applications.sheets; /**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
export default class DhpEnvironment extends DaggerheartSheet(ActorSheetV2) {
export default class DhpEnvironment extends DHBaseActorSheet {
/**@inheritdoc */
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
tag: 'form', classes: ['environment'],
classes: ['daggerheart', 'sheet', 'actor', 'dh-style', 'environment'],
position: { position: {
width: 500 width: 500
}, },
actions: { actions: {
addAdversary: this.addAdversary,
deleteProperty: this.deleteProperty,
openSettings: this.openSettings,
useItem: this.useItem, useItem: this.useItem,
toChat: this.toChat toChat: this.toChat
}, },
form: {
handler: this._updateForm,
submitOnChange: true,
closeOnSubmit: false
},
dragDrop: [{ dragSelector: '.action-section .inventory-item', dropSelector: null }] dragDrop: [{ dragSelector: '.action-section .inventory-item', dropSelector: null }]
}; };
/**@override */
static PARTS = { static PARTS = {
header: { template: 'systems/daggerheart/templates/sheets/actors/environment/header.hbs' }, header: { template: 'systems/daggerheart/templates/sheets/actors/environment/header.hbs' },
features: { template: 'systems/daggerheart/templates/sheets/actors/environment/features.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' } notes: { template: 'systems/daggerheart/templates/sheets/actors/environment/notes.hbs' }
}; };
/** @inheritdoc */
static TABS = { static TABS = {
features: { primary: {
active: true, tabs: [{ id: 'features' }, { id: 'potentialAdversaries' }, { id: 'notes' }],
cssClass: '', initial: 'features',
group: 'primary', labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
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'
} }
}; };
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) { getItem(element) {
const itemId = (element.target ?? element).closest('[data-item-id]').dataset.itemId, const itemId = (element.target ?? element).closest('[data-item-id]').dataset.itemId,
@ -75,33 +43,14 @@ export default class DhpEnvironment extends DaggerheartSheet(ActorSheetV2) {
return item; return item;
} }
static async openSettings() { /* -------------------------------------------- */
await new DHEnvironmentSettings(this.document).render(true); /* Application Clicks Actions */
} /* -------------------------------------------- */
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();
}
/**
*
* @type {ApplicationClickAction}
*/
async viewAdversary(_, button) { async viewAdversary(_, button) {
const target = button.closest('[data-item-uuid]'); const target = button.closest('[data-item-uuid]');
const adversary = await foundry.utils.fromUuid(target.dataset.itemUuid); const adversary = await foundry.utils.fromUuid(target.dataset.itemUuid);
@ -110,7 +59,7 @@ export default class DhpEnvironment extends DaggerheartSheet(ActorSheetV2) {
return; return;
} }
adversary.sheet.render(true); adversary.sheet.render({ force: true });
} }
static async useItem(event, button) { static async useItem(event, button) {

View file

@ -1,3 +1,5 @@
export { default as DHApplicationMixin } from './application-mixin.mjs'; export { default as DHApplicationMixin } from './application-mixin.mjs';
export { default as DHBaseItemSheet } from './base-item.mjs'; export { default as DHBaseItemSheet } from './base-item.mjs';
export { default as DHHeritageSheet } from './heritage-sheet.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';

View file

@ -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<DocumentSheetV2>}
*/
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;
}
}

View file

@ -6,6 +6,14 @@ import { tagifyElement } from '../../../helpers/utils.mjs';
* @property {string} [dragSelector] - A CSS selector that identifies draggable elements. * @property {string} [dragSelector] - A CSS selector that identifies draggable elements.
* @property {string} [dropSelector] - A CSS selector that identifies drop targets. * @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 * @typedef {Object} TagOption
* @property {string} label * @property {string} label
* @property {string} [src] * @property {string} [src]
@ -24,9 +32,17 @@ import { tagifyElement } from '../../../helpers/utils.mjs';
* *
* @typedef {Object} TagifyOptions * @typedef {Object} TagifyOptions
* @property {number} [maxTags] - Maximum number of allowed tags * @property {number} [maxTags] - Maximum number of allowed tags
* */
/**
* @typedef {import("@client/applications/api/handlebars-application.mjs").HandlebarsRenderOptions} HandlebarsRenderOptions * @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 = { static DEFAULT_OPTIONS = {
classes: ['daggerheart', 'sheet', 'dh-style'], classes: ['daggerheart', 'sheet', 'dh-style'],
position: {
width: 480,
height: 'auto'
},
actions: { actions: {
addEffect: DHSheetV2.#addEffect, createDoc: DHSheetV2.#createDoc,
editEffect: DHSheetV2.#editEffect, editDoc: DHSheetV2.#editDoc,
removeEffect: DHSheetV2.#removeEffect deleteDoc: DHSheetV2.#deleteDoc
}, },
contextMenus: [],
dragDrop: [], dragDrop: [],
tagifyConfigs: [] tagifyConfigs: []
}; };
@ -74,6 +87,11 @@ export default function DHApplicationMixin(Base) {
super._attachPartListeners(partId, htmlElement, options); super._attachPartListeners(partId, htmlElement, options);
this._dragDrop.forEach(d => d.bind(htmlElement)); 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 */ /**@inheritdoc */
async _onRender(context, options) { async _onRender(context, options) {
@ -81,6 +99,10 @@ export default function DHApplicationMixin(Base) {
this._createTagifyElements(this.options.tagifyConfigs); this._createTagifyElements(this.options.tagifyConfigs);
} }
/* -------------------------------------------- */
/* Tags */
/* -------------------------------------------- */
/** /**
* Creates Tagify elements from configuration objects * Creates Tagify elements from configuration objects
* @param {TagifyConfig[]} tagConfigs - Array of Tagify configuration objects * @param {TagifyConfig[]} tagConfigs - Array of Tagify configuration objects
@ -150,22 +172,38 @@ export default function DHApplicationMixin(Base) {
_onDrop(event) {} _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. * Get the set of ContextMenu options which should be used for journal entry pages in the sidebar.
* @param {object} options * @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]}
* @param {string} [objectPath='document'] * @protected
* @returns {Promise<object>}
* @inheritdoc
*/ */
async _prepareContext(options, objectPath = 'document') { _getEntryContextOptions() {
return [];
}
/* -------------------------------------------- */
/* Prepare Context */
/* -------------------------------------------- */
/**@inheritdoc*/
async _prepareContext(options) {
const context = await super._prepareContext(options); const context = await super._prepareContext(options);
context.config = CONFIG.DH; context.config = CONFIG.DH;
context.source = this[objectPath]; context.source = this.document;
context.fields = this[objectPath].schema.fields; context.fields = this.document.schema.fields;
context.systemFields = this[objectPath].system ? this[objectPath].system.schema.fields : {}; context.systemFields = this.document.system.schema.fields;
return context; 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 {PointerEvent} event - The originating click event
* @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"] * @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"]
*/ */
static async #addEffect() { static async #createDoc(event, button) {
const cls = foundry.documents.ActiveEffect; const { documentClass, type } = button.dataset;
await cls.create( console.log(documentClass, type);
{ const parent = this.document;
name: game.i18n.format('DOCUMENT.New', { type: game.i18n.localize(cls.metadata.label) })
}, const cls = getDocumentClass(documentClass);
{ parent: this.document } 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 {PointerEvent} event - The originating click event
* @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"] * @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"]
*/ */
static async #editEffect(_event, button) { static #editDoc(_event, button) {
const effect = this.document.effects.get(button.dataset.effect); const { type, docId } = button.dataset;
effect.sheet.render({ force: true }); 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 {PointerEvent} _event - The originating click event
* @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"] * @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"]
*/ */
static async #removeEffect(_event, button) { static async #deleteDoc(_event, button) {
await this.document.effects.get(button.dataset.effect).delete(); const { type, docId } = button.dataset;
await this.document.getEmbeddedDocument(type, docId, { strict: true }).delete();
} }
} }

View file

@ -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 });
}
}

View file

@ -3,6 +3,8 @@ import DHApplicationMixin from './application-mixin.mjs';
const { ItemSheetV2 } = foundry.applications.sheets; const { ItemSheetV2 } = foundry.applications.sheets;
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
/** /**
* A base item sheet extending {@link ItemSheetV2} via {@link DHApplicationMixin} * A base item sheet extending {@link ItemSheetV2} via {@link DHApplicationMixin}
* @extends ItemSheetV2 * @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. * Add a new action to the item, prompting the user for its type.
* @param {PointerEvent} _event - The originating click event * @type {ApplicationClickAction}
* @param {HTMLElement} _button - The capturing HTML element which defines the [data-action="addAction"]
*/ */
static async #addAction(_event, _button) { static async #addAction(_event, _button) {
const actionType = await DHBaseItemSheet.selectActionType(); const actionType = await DHBaseItemSheet.selectActionType();
@ -124,8 +125,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
/** /**
* Edit an existing action on the item * Edit an existing action on the item
* @param {PointerEvent} _event - The originating click event * @type {ApplicationClickAction}
* @param {HTMLElement} button - The capturing HTML element which defines the [data-action="editAction"]
*/ */
static async #editAction(_event, button) { static async #editAction(_event, button) {
const action = this.document.system.actions[button.dataset.index]; 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. * Remove an action from the item.
* @param {PointerEvent} event - The originating click event * @type {ApplicationClickAction}
* @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"]
*/ */
static async #removeAction(event, button) { static async #removeAction(event, button) {
event.stopPropagation(); 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. * Add a new feature to the item, prompting the user for its type.
* @param {PointerEvent} _event - The originating click event * @type {ApplicationClickAction}
* @param {HTMLElement} _button - The capturing HTML element which defines the [data-action="addFeature"]
*/ */
static async #addFeature(_event, _button) { static async #addFeature(_event, _button) {
const feature = await game.items.documentClass.create({ 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 * Edit an existing feature on the item
* @param {PointerEvent} _event - The originating click event * @type {ApplicationClickAction}
* @param {HTMLElement} button - The capturing HTML element which defines the [data-action="editFeature"]
*/ */
static async #editFeature(_event, button) { static async #editFeature(_event, button) {
const target = button.closest('.feature-item'); const target = button.closest('.feature-item');
@ -178,8 +175,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
/** /**
* Remove a feature from the item. * Remove a feature from the item.
* @param {PointerEvent} event - The originating click event * @type {ApplicationClickAction}
* @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeFeature"]
*/ */
static async #removeFeature(event, button) { static async #removeFeature(event, button) {
event.stopPropagation(); event.stopPropagation();

View file

@ -1,5 +1,7 @@
const { HandlebarsApplicationMixin } = foundry.applications.api; const { HandlebarsApplicationMixin } = foundry.applications.api;
//Just used by action config
export default function DhpApplicationMixin(Base) { export default function DhpApplicationMixin(Base) {
return class DhpSheetV2 extends HandlebarsApplicationMixin(Base) { return class DhpSheetV2 extends HandlebarsApplicationMixin(Base) {
constructor(options = {}) { constructor(options = {}) {

View file

@ -1,3 +1,4 @@
import DHAdversarySettings from '../../applications/sheets-configs/adversary-settings.mjs';
import ActionField from '../fields/actionField.mjs'; import ActionField from '../fields/actionField.mjs';
import BaseDataActor from './base.mjs'; import BaseDataActor from './base.mjs';
@ -13,7 +14,8 @@ export default class DhpAdversary extends BaseDataActor {
static get metadata() { static get metadata() {
return foundry.utils.mergeObject(super.metadata, { return foundry.utils.mergeObject(super.metadata, {
label: 'TYPES.Actor.adversary', label: 'TYPES.Actor.adversary',
type: 'adversary' type: 'adversary',
settingSheet: DHAdversarySettings,
}); });
} }

View file

@ -1,18 +1,29 @@
import DHBaseActorSettings from "../../applications/sheets/api/actor-setting.mjs";
/** /**
* Describes metadata about the actor data model type * Describes metadata about the actor data model type
* @typedef {Object} ActorDataModelMetadata * @typedef {Object} ActorDataModelMetadata
* @property {string} label - A localizable label used on application. * @property {string} label - A localizable label used on application.
* @property {string} type - The system type that this data model represents. * @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 { export default class BaseDataActor extends foundry.abstract.TypeDataModel {
/** @returns {ActorDataModelMetadata}*/ /** @returns {ActorDataModelMetadata}*/
static get metadata() { static get metadata() {
return { return {
label: 'Base Actor', label: 'Base Actor',
type: 'base' type: 'base',
isNPC: true,
settingSheet: null,
}; };
} }
/**@returns {ActorDataModelMetadata}*/
get metadata() {
return this.constructor.metadata;
}
/** @inheritDoc */ /** @inheritDoc */
static defineSchema() { static defineSchema() {
const fields = foundry.data.fields; const fields = foundry.data.fields;

View file

@ -28,7 +28,8 @@ export default class DhCharacter extends BaseDataActor {
static get metadata() { static get metadata() {
return foundry.utils.mergeObject(super.metadata, { return foundry.utils.mergeObject(super.metadata, {
label: 'TYPES.Actor.character', label: 'TYPES.Actor.character',
type: 'character' type: 'character',
isNPC: false,
}); });
} }

View file

@ -3,6 +3,7 @@ import DhLevelData from '../levelData.mjs';
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs'; import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
import ActionField from '../fields/actionField.mjs'; import ActionField from '../fields/actionField.mjs';
import { adjustDice, adjustRange } from '../../helpers/utils.mjs'; import { adjustDice, adjustRange } from '../../helpers/utils.mjs';
import DHCompanionSettings from '../../applications/sheets-configs/companion-settings.mjs';
export default class DhCompanion extends BaseDataActor { export default class DhCompanion extends BaseDataActor {
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Companion']; static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Companion'];
@ -10,7 +11,8 @@ export default class DhCompanion extends BaseDataActor {
static get metadata() { static get metadata() {
return foundry.utils.mergeObject(super.metadata, { return foundry.utils.mergeObject(super.metadata, {
label: 'TYPES.Actor.companion', label: 'TYPES.Actor.companion',
type: 'companion' type: 'companion',
settingSheet: DHCompanionSettings
}); });
} }

View file

@ -1,5 +1,6 @@
import BaseDataActor from './base.mjs'; import BaseDataActor from './base.mjs';
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs'; import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
import DHEnvironmentSettings from '../../applications/sheets-configs/environment-settings.mjs';
export default class DhEnvironment extends BaseDataActor { export default class DhEnvironment extends BaseDataActor {
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Environment']; static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Environment'];
@ -7,7 +8,8 @@ export default class DhEnvironment extends BaseDataActor {
static get metadata() { static get metadata() {
return foundry.utils.mergeObject(super.metadata, { return foundry.utils.mergeObject(super.metadata, {
label: 'TYPES.Actor.environment', label: 'TYPES.Actor.environment',
type: 'environment' type: 'environment',
settingSheet: DHEnvironmentSettings
}); });
} }

View file

@ -22,6 +22,11 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
}; };
} }
/**@returns {ItemDataModelMetadata}*/
get metadata() {
return this.constructor.metadata;
}
/** @inheritDoc */ /** @inheritDoc */
static defineSchema() { static defineSchema() {
const schema = {}; const schema = {};
@ -60,9 +65,9 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
/**@inheritdoc */ /**@inheritdoc */
async _preCreate(data, options, user) { async _preCreate(data, options, user) {
// Skip if no initial action is required or actions already exist // 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 actionType = { weapon: 'attack' }[metadataType];
const ActionClass = game.system.api.models.actions.actionsTypes[actionType]; const ActionClass = game.system.api.models.actions.actionsTypes[actionType];

View file

@ -4,7 +4,16 @@ import DamageReductionDialog from '../applications/dialogs/damageReductionDialog
import { LevelOptionType } from '../data/levelTier.mjs'; import { LevelOptionType } from '../data/levelTier.mjs';
import DHFeature from '../data/item/feature.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) { async _preCreate(data, options, user) {
if ((await super._preCreate(data, options, user)) === false) return false; 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; const modifier = roll.modifier !== null ? Number.parseInt(roll.modifier) : null;
return modifier !== null return modifier !== null
? [ ? [
{ {
value: modifier, value: modifier,
label: roll.label label: roll.label
? modifier >= 0 ? modifier >= 0
? `${roll.label} +${modifier}` ? `${roll.label} +${modifier}`
: `${roll.label} ${modifier}` : `${roll.label} ${modifier}`
: null, : null,
title: roll.label title: roll.label
} }
] ]
: []; : [];
} }
@ -440,10 +449,10 @@ export default class DhpActor extends Actor {
damage >= this.system.damageThresholds.severe damage >= this.system.damageThresholds.severe
? 3 ? 3
: damage >= this.system.damageThresholds.major : damage >= this.system.damageThresholds.major
? 2 ? 2
: damage >= this.system.damageThresholds.minor : damage >= this.system.damageThresholds.minor
? 1 ? 1
: 0; : 0;
if ( if (
this.type === 'character' && this.type === 'character' &&

View file

@ -34,7 +34,7 @@ export default class DHItem extends foundry.documents.Item {
* @returns {boolean} Returns `true` if the item is an inventory item. * @returns {boolean} Returns `true` if the item is an inventory item.
*/ */
get isInventoryItem() { get isInventoryItem() {
return this.system.constructor.metadata.isInventoryItem ?? false; return this.system.metadata.isInventoryItem ?? false;
} }
/** @inheritdoc */ /** @inheritdoc */
@ -53,17 +53,17 @@ export default class DHItem extends foundry.documents.Item {
const isInventoryItem = CONFIG.Item.dataModels[type]?.metadata?.isInventoryItem; const isInventoryItem = CONFIG.Item.dataModels[type]?.metadata?.isInventoryItem;
const group = const group =
isInventoryItem === true isInventoryItem === true
? 'Inventory Items' ? 'Inventory Items' //TODO localize
: isInventoryItem === false : isInventoryItem === false
? 'Character Items' ? 'Character Items' //TODO localize
: 'Other'; : 'Other'; //TODO localize
return { value: type, label, group }; return { value: type, label, group };
} }
); );
if (!documentTypes.length) { 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)); const sortedTypes = documentTypes.sort((a, b) => a.label.localeCompare(b.label, game.i18n.lang));

View file

@ -3,7 +3,7 @@
data-tab='{{tabs.experiences.id}}' data-tab='{{tabs.experiences.id}}'
data-group='{{tabs.experiences.group}}' data-group='{{tabs.experiences.group}}'
> >
<button class="add-experience-btn" type="button" data-action="addExperience"> <button type="button" class="add-experience-btn" type="button" data-action="addExperience">
<span>New Experience</span> <span>New Experience</span>
</button> </button>

View file

@ -3,7 +3,7 @@
data-tab='{{tabs.features.id}}' data-tab='{{tabs.features.id}}'
data-group='{{tabs.features.group}}' data-group='{{tabs.features.group}}'
> >
<button class="add-feature-btn" data-action="addFeature"> <button type="button" class="add-feature-btn" data-action="createDoc" data-document-class="Item" data-type="feature">
{{localize "DOCUMENT.New" type=(localize "TYPES.Item.feature")}} {{localize "DOCUMENT.New" type=(localize "TYPES.Item.feature")}}
</button> </button>
<fieldset> <fieldset>
@ -16,8 +16,8 @@
<span>{{feature.name}}</span> <span>{{feature.name}}</span>
</div> </div>
<div class="controls"> <div class="controls">
<a data-action="editFeature" id="{{feature.id}}" data-tooltip="{{localize 'DAGGERHEART.Tooltip.edit'}}"><i class="fa-solid fa-pen-to-square"></i></a> <a data-action="editDoc" data-type="Item" data-doc-id="{{feature.id}}" data-tooltip="{{localize 'DAGGERHEART.Tooltip.edit'}}"><i class="fa-solid fa-pen-to-square"></i></a>
<a data-action="removeFeature" id="{{feature.id}}" data-tooltip="{{localize 'CONTROLS.CommonDelete'}}"><i class="fa-solid fa-trash"></i></a> <a data-action="deleteDoc" data-type="Item" data-doc-id="{{feature.id}}" data-tooltip="{{localize 'CONTROLS.CommonDelete'}}"><i class="fa-solid fa-trash"></i></a>
</div> </div>
</li> </li>
{{/each}} {{/each}}

View file

@ -13,11 +13,11 @@
<div class="form-group"> <div class="form-group">
<div class="form-fields"> <div class="form-fields">
<label>{{localize "DAGGERHEART.ACTORS.Companion.FIELDS.partner.label"}}</label> <label>{{localize "DAGGERHEART.ACTORS.Companion.FIELDS.partner.label"}}</label>
<select class="partner-value"> <select class="partner-value" name="system.partner">
{{selectOptions playerCharacters selected=document.system.partner.uuid labelAttr="name" valueAttr="key" blank=""}} {{selectOptions playerCharacters selected=document.system.partner.uuid labelAttr="name" valueAttr="key" blank=""}}
</select> </select>
</div> </div>
</div> </div>
</fieldset> </fieldset>
<button data-action="levelUp" {{#if (not document.system.levelData.canLevelUp)}}disabled{{/if}}>Level Up</button> <button type="button" data-action="levelUp" {{#if (not document.system.levelData.canLevelUp)}}disabled{{/if}}>Level Up</button>
</section> </section>

View file

@ -3,18 +3,20 @@
data-tab='{{tabs.adversaries.id}}' data-tab='{{tabs.adversaries.id}}'
data-group='{{tabs.adversaries.group}}' data-group='{{tabs.adversaries.group}}'
> >
<button class="add-action-btn" data-action="addCategory"> <button type="button" class="add-action-btn" data-action="addCategory">
New Category New Category
</button> </button>
{{#each document.system.potentialAdversaries}} {{#each document.system.potentialAdversaries as |category id|}}
<fieldset class="category-container" data-potential-adversary="{{@key}}"> <fieldset class="category-container" data-potential-adversary="{{id}}">
<legend>{{this.label}}</legend> <legend>{{category.label}}</legend>
<div class="category-name"> <div class="category-name">
<input type="text" name="{{concat "system.potentialAdversaries." @key ".label" }}" value="{{this.label}}" /> <input type="text" name="{{concat "system.potentialAdversaries." @key ".label" }}" value="{{category.label}}" />
<a><i class="fa-solid fa-trash" data-action="deleteProperty" data-path="system.potentialAdversaries" id={{@key}} data-tooltip='{{localize "CONTROLS.CommonDelete"}}'></i></a> <a data-action="removeCategory" data-category-id={{id}} data-tooltip='{{localize "CONTROLS.CommonDelete"}}'>
<i class="fa-solid fa-trash"></i>
</a>
</div> </div>
<div class="adversaries-container"> <div class="adversaries-container">
{{#each this.adversaries as |adversary|}} {{#each category.adversaries as |adversary|}}
<div class="adversary-container"> <div class="adversary-container">
{{> 'systems/daggerheart/templates/sheets/global/partials/inventory-item.hbs' item=adversary type='adversary' isActor=true categoryAdversary=@../key}} {{> 'systems/daggerheart/templates/sheets/global/partials/inventory-item.hbs' item=adversary type='adversary' isActor=true categoryAdversary=@../key}}
</div> </div>

View file

@ -3,7 +3,7 @@
data-tab='{{tabs.features.id}}' data-tab='{{tabs.features.id}}'
data-group='{{tabs.features.group}}' data-group='{{tabs.features.group}}'
> >
<button class="add-feature-btn" data-action="addFeature"> <button type="button" class="add-feature-btn" data-action="createDoc" data-document-class="Item" data-type="feature">
{{localize "DOCUMENT.New" type=(localize "TYPES.Item.feature")}} {{localize "DOCUMENT.New" type=(localize "TYPES.Item.feature")}}
</button> </button>
<fieldset> <fieldset>
@ -16,8 +16,8 @@
<span>{{feature.name}}</span> <span>{{feature.name}}</span>
</div> </div>
<div class="controls"> <div class="controls">
<a data-action="editFeature" id="{{feature.id}}" data-tooltip="{{localize 'DAGGERHEART.Tooltip.edit'}}"><i class="fa-solid fa-pen-to-square"></i></a> <a data-action="editDoc" data-type="Item" data-doc-id="{{feature.id}}" data-tooltip="{{localize 'DAGGERHEART.Tooltip.edit'}}"><i class="fa-solid fa-pen-to-square"></i></a>
<a data-action="removeFeature" id="{{feature.id}}" data-tooltip="{{localize 'CONTROLS.CommonDelete'}}"><i class="fa-solid fa-trash"></i></a> <a data-action="deleteDoc" data-type="Item" data-doc-id="{{feature.id}}" data-tooltip="{{localize 'CONTROLS.CommonDelete'}}"><i class="fa-solid fa-trash"></i></a>
</div> </div>
</li> </li>
{{/each}} {{/each}}

View file

@ -13,7 +13,8 @@
<div class='level-div'> <div class='level-div'>
<h3 class='label'> <h3 class='label'>
{{#if (or document.system.needsCharacterSetup document.system.levelData.canLevelUp)}} {{#if (or document.system.needsCharacterSetup document.system.levelData.canLevelUp)}}
<button <button
type="button"
class="level-button glow" data-tooltip="{{#if document.system.needsCharacterSetup}}{{localize "DAGGERHEART.Sheets.PC.CharacterSetup"}}{{else}}{{localize "DAGGERHEART.ACTORS.Character.levelUp"}}{{/if}}" class="level-button glow" data-tooltip="{{#if document.system.needsCharacterSetup}}{{localize "DAGGERHEART.Sheets.PC.CharacterSetup"}}{{else}}{{localize "DAGGERHEART.ACTORS.Character.levelUp"}}{{/if}}"
data-action="levelManagement" data-action="levelManagement"
> >
@ -28,42 +29,42 @@
<div class="character-details"> <div class="character-details">
<div> <div>
{{#if document.system.class.value}} {{#if document.system.class.value}}
<span data-action="viewObject" data-value="{{document.system.class.value.uuid}}">{{document.system.class.value.name}}</span> <span data-action="editDoc" data-type="Item" data-doc-id="{{document.system.class.value.id}}">{{document.system.class.value.name}}</span>
{{else}} {{else}}
<span>{{localize 'TYPES.Item.class'}}</span> <span data-action="openPack" data-key="daggerheart.classes">{{localize 'TYPES.Item.class'}}</span>
{{/if}} {{/if}}
<span class="dot">•</span> <span class="dot">•</span>
{{#if document.system.class.subclass}} {{#if document.system.class.subclass}}
<span data-action="viewObject" data-value="{{document.system.class.subclass.uuid}}">{{document.system.class.subclass.name}}</span> <span data-action="editDoc" data-type="Item" data-doc-id="{{document.system.class.subclass.id}}">{{document.system.class.subclass.name}}</span>
{{else}} {{else}}
<span data-action="selectSubclass">{{localize 'TYPES.Item.subclass'}}</span> <span data-action="openPack" data-key="daggerheart.subclass">{{localize 'TYPES.Item.subclass'}}</span>
{{/if}} {{/if}}
<span class="dot">•</span> <span class="dot">•</span>
{{#if document.system.community}} {{#if document.system.community}}
<span data-action="viewObject" data-value="{{document.system.community.uuid}}">{{document.system.community.name}}</span> <span data-action="editDoc" data-type="Item" data-doc-id="{{document.system.community.id}}">{{document.system.community.name}}</span>
{{else}} {{else}}
<span data-action="selectCommunity">{{localize 'TYPES.Item.community'}}</span> <span data-action="openPack" data-key="daggerheart.community">{{localize 'TYPES.Item.community'}}</span>
{{/if}} {{/if}}
<span class="dot">•</span> <span class="dot">•</span>
{{#if document.system.ancestry}} {{#if document.system.ancestry}}
<span data-action="viewObject" data-value="{{document.system.ancestry.uuid}}">{{document.system.ancestry.name}}</span> <span data-action="editDoc" data-type="Item" data-doc-id="{{document.system.ancestry.id}}">{{document.system.ancestry.name}}</span>
{{else}} {{else}}
<span data-action="selectAncestry">{{localize 'TYPES.Item.ancestry'}}</span> <span data-action="openPack" data-key="daggerheart.ancestry">{{localize 'TYPES.Item.ancestry'}}</span>
{{/if}} {{/if}}
</div> </div>
{{#if document.system.multiclass.value}} {{#if document.system.multiclass.value}}
<div class="multiclass"> <div class="multiclass">
{{#if document.system.multiclass.value}} {{#if document.system.multiclass.value}}
<span data-action="viewObject" data-value="{{document.system.multiclass.value.uuid}}">{{document.system.multiclass.value.name}}</span> <span data-action="editDoc" data-type="Item" data-doc-id="{{document.system.multiclass.value.id}}">{{document.system.multiclass.value.name}}</span>
{{else}} {{else}}
<span>{{localize 'DAGGERHEART.GENERAL.multiclass'}}</span> <span data-action="openPack" data-key="daggerheart.classes">{{localize 'DAGGERHEART.GENERAL.multiclass'}}</span>
{{/if}} {{/if}}
<span class="dot">•</span> <span class="dot">•</span>
{{#if document.system.multiclass.subclass}} {{#if document.system.multiclass.subclass}}
<span data-action="viewObject" data-value="{{document.system.multiclass.subclass.uuid}}">{{document.system.multiclass.subclass.name}}</span> <span data-action="editDoc" data-type="Item" data-doc-id="{{document.system.multiclass.subclass.id}}">{{document.system.multiclass.subclass.name}}</span>
{{else}} {{else}}
<span data-action="selectSubclass">{{localize 'TYPES.Item.subclass'}}</span> <span data-action="openPack" data-key="daggerheart.subclass">{{localize 'TYPES.Item.subclass'}}</span>
{{/if}} {{/if}}
</div> </div>
{{/if}} {{/if}}
@ -95,7 +96,7 @@
<div class="character-traits"> <div class="character-traits">
{{#each this.attributes as |attribute key|}} {{#each this.attributes as |attribute key|}}
<div class="trait" data-tooltip="{{#each attribute.verbs}}{{this}}<br>{{/each}}" data-action="attributeRoll" data-attribute="{{key}}" data-value="{{attribute.total}}"> <div class="trait" data-tooltip="{{#each attribute.verbs}}{{this}}<br>{{/each}}" data-action="rollAttribute" data-attribute="{{key}}" data-value="{{attribute.total}}">
<div class="trait-name"> <div class="trait-name">
<span>{{localize (concat 'DAGGERHEART.CONFIG.Traits.' key '.short')}}</span> <span>{{localize (concat 'DAGGERHEART.CONFIG.Traits.' key '.short')}}</span>
{{#if attribute.tierMarked}} {{#if attribute.tierMarked}}

View file

@ -1,8 +1,5 @@
<section <section class='tab {{tabs.loadout.cssClass}} {{tabs.loadout.id}}' data-tab='{{tabs.loadout.id}}'
class='tab {{tabs.loadout.cssClass}} {{tabs.loadout.id}}' data-group='{{tabs.loadout.group}}'>
data-tab='{{tabs.loadout.id}}'
data-group='{{tabs.loadout.group}}'
>
<div class="search-section"> <div class="search-section">
<div class="search-bar"> <div class="search-bar">
<div class="icon"> <div class="icon">
@ -13,29 +10,28 @@
<a class="filter-button"> <a class="filter-button">
<i class="fa-solid fa-filter"></i> <i class="fa-solid fa-filter"></i>
</a> </a>
<button class="btn-toggle-view" data-action="toggleLoadoutView" data-value="{{this.abilities.loadout.listView}}"> <button type="button" class="btn-toggle-view" data-action="toggleLoadoutView" data-value="{{listView}}">
<span class="{{#if this.abilities.loadout.listView}}list-active{{/if}} list-icon"> <span class="{{ifThen listView 'list-active' ''}} list-icon">
<i class="fa-solid fa-bars"></i> <i class="fa-solid fa-bars"></i>
</span> </span>
<span class="{{#unless this.abilities.loadout.listView}}grid-active{{/unless}} grid-icon"> <span class="{{ifThen listView '' 'grid-active'}} grid-icon">
<i class="fa-solid fa-grip"></i> <i class="fa-solid fa-grip"></i>
</span> </span>
</button> </button>
</div> </div>
<div class="items-section"> <div class="items-section">
{{#if this.abilities.loadout.listView}} {{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs'
{{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' title=(localize 'DAGGERHEART.GENERAL.Tabs.loadout') type='domainCard' isGlassy=true cardView='list'}} title=(localize 'DAGGERHEART.GENERAL.Tabs.loadout')
{{else}} type='domainCard'
{{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' title=(localize 'DAGGERHEART.GENERAL.Tabs.loadout') type='domainCard' isGlassy=true cardView='card'}} isGlassy=true
{{/if}} cardView=(ifThen listView "list" "card")}}
{{#if this.abilities.loadout.listView}} {{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs'
{{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' title=(localize 'DAGGERHEART.GENERAL.Tabs.vault') type='domainCard' isVault=true isGlassy=true cardView='list'}} title=(localize 'DAGGERHEART.GENERAL.Tabs.vault')
{{else}} type='domainCard'
{{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' title=(localize 'DAGGERHEART.GENERAL.Tabs.vault') type='domainCard' isVault=true isGlassy=true cardView='card'}} isVault=true
{{/if}} isGlassy=true
cardView=(ifThen listView "list" "card")}}
</div> </div>
</section> </section>

View file

@ -1,7 +1,7 @@
<aside class="character-sidebar-sheet"> <aside class="character-sidebar-sheet">
<div class="portrait {{#if document.system.deathMoveViable}}death-roll{{/if}}"> <div class="portrait {{#if isDeath}}death-roll{{/if}}">
<img src="{{document.img}}" alt="{{document.name}}" data-action='editImage' data-edit="img"> <img src="{{document.img}}" alt="{{document.name}}" data-action='editImage' data-edit="img">
<a class="death-roll-btn" data-tooltip="{{localize "DAGGERHEART.UI.Tooltip.makeDeathMove"}}" data-action="makeDeathMove"><i class="fas fa-skull death-save" ></i></a> <a class="death-roll-btn" data-tooltip="{{localize "DAGGERHEART.UI.Tooltip.makeDeathMove"}}" {{#if isDeath}}data-action="makeDeathMove"{{/if}}><i class="fas fa-skull death-save" ></i></a>
</div> </div>
<div class="info-section"> <div class="info-section">

View file

@ -39,7 +39,7 @@
</div> </div>
<div class="companion-navigation"> <div class="companion-navigation">
{{> 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs'}} {{> 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs'}}
<button data-action="openSettings"> <button type="button" data-action="openSettings">
<i class="fa-solid fa-wrench"></i> <i class="fa-solid fa-wrench"></i>
</button> </button>
</div> </div>

View file

@ -42,7 +42,7 @@
</div> </div>
<div class="environment-navigation"> <div class="environment-navigation">
{{> 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs'}} {{> 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs'}}
<button data-action="openSettings"> <button type="button" data-action="openSettings">
<i class="fa-solid fa-wrench"></i> <i class="fa-solid fa-wrench"></i>
</button> </button>
</div> </div>

View file

@ -3,12 +3,12 @@
<div class="card-label"> <div class="card-label">
<div class="controls"> <div class="controls">
{{#if (eq type 'weapon')}} {{#if (eq type 'weapon')}}
<a class="{{#unless item.system.equipped}}unequipped{{/unless}}" data-action="toggleEquipItem" id="{{item.id}}" data-tooltip="{{#unless item.system.equipped}}{{localize 'CONTROLS.CommonEdit'}}{{else}}{{localize 'DAGGERHEART.UI.Tooltip.unequip'}}{{/unless}}"> <a class="{{#unless item.system.equipped}}unequipped{{/unless}}" data-action="toggleEquipItem" data-tooltip="{{#unless item.system.equipped}}{{localize 'DAGGERHEART.UI.Tooltip.equip'}}{{else}}{{localize 'DAGGERHEART.UI.Tooltip.unequip'}}{{/unless}}">
<i class="fa-solid fa-hands"></i> <i class="fa-solid fa-hands"></i>
</a> </a>
{{/if}} {{/if}}
{{#if (eq type 'armor')}} {{#if (eq type 'armor')}}
<a class="{{#unless item.system.equipped}}unequipped{{/unless}}" data-action="toggleEquipItem" id="{{item.id}}" data-tooltip="{{#unless item.system.equipped}}{{localize 'CONTROLS.CommonEdit'}}{{else}}{{localize 'DAGGERHEART.UI.Tooltip.unequip'}}{{/unless}}"> <a class="{{#unless item.system.equipped}}unequipped{{/unless}}" data-action="toggleEquipItem" data-tooltip="{{#unless item.system.equipped}}{{localize 'DAGGERHEART.UI.Tooltip.equip'}}{{else}}{{localize 'DAGGERHEART.UI.Tooltip.unequip'}}{{/unless}}">
<i class="fa-solid fa-shield"></i> <i class="fa-solid fa-shield"></i>
</a> </a>
{{/if}} {{/if}}

View file

@ -142,12 +142,12 @@
{{else}} {{else}}
<div class="controls"> <div class="controls">
{{#if (eq type 'weapon')}} {{#if (eq type 'weapon')}}
<a class="{{#unless item.system.equipped}}unequipped{{/unless}}" data-action="toggleEquipItem" data-tooltip="{{#unless item.system.equipped}}{{localize 'CONTROLS.CommonEdit'}}{{else}}{{localize 'DAGGERHEART.UI.Tooltip.unequip'}}{{/unless}}"> <a class="{{#unless item.system.equipped}}unequipped{{/unless}}" data-action="toggleEquipItem" data-tooltip="{{#unless item.system.equipped}}{{localize 'DAGGERHEART.UI.Tooltip.equip'}}{{else}}{{localize 'DAGGERHEART.UI.Tooltip.unequip'}}{{/unless}}">
<i class="fa-solid fa-hands"></i> <i class="fa-solid fa-hands"></i>
</a> </a>
{{/if}} {{/if}}
{{#if (eq type 'armor')}} {{#if (eq type 'armor')}}
<a class="{{#unless item.system.equipped}}unequipped{{/unless}}" data-action="toggleEquipItem" data-tooltip="{{#unless item.system.equipped}}{{localize 'CONTROLS.CommonEdit'}}{{else}}{{localize 'DAGGERHEART.UI.Tooltip.unequip'}}{{/unless}}"> <a class="{{#unless item.system.equipped}}unequipped{{/unless}}" data-action="toggleEquipItem" data-tooltip="{{#unless item.system.equipped}}{{localize 'DAGGERHEART.UI.Tooltip.equip'}}{{else}}{{localize 'DAGGERHEART.UI.Tooltip.unequip'}}{{/unless}}">
<i class="fa-solid fa-shield"></i> <i class="fa-solid fa-shield"></i>
</a> </a>
{{/if}} {{/if}}

View file

@ -4,15 +4,20 @@
data-group='{{tabs.effects.group}}' data-group='{{tabs.effects.group}}'
> >
<fieldset class="one-column"> <fieldset class="one-column">
<legend>{{localize "DAGGERHEART.GENERAL.Effect.plural"}} <a><i class="fa-solid fa-plus icon-button" data-action="addEffect"></i></a></legend> <legend>
{{localize "DAGGERHEART.GENERAL.Effect.plural"}}
<a data-action="createDoc" data-document-class="ActiveEffect" data-type="base">
<i class="fa-solid fa-plus icon-button"></i>
</a>
</legend>
<div class="effects-list"> <div class="effects-list">
{{#each document.effects as |effect|}} {{#each document.effects as |effect|}}
<div class="effect-item"> <div class="effect-item">
<img class="image" src="{{effect.img}}" /> <img class="image" src="{{effect.img}}" />
<span>{{effect.name}}</span> <span>{{effect.name}}</span>
<div class="controls"> <div class="controls">
<a data-action="editEffect" data-effect="{{effect.id}}"><i class="fa-solid fa-pen-to-square"></i></a> <a data-action="editDoc" data-type="ActiveEffect" data-doc-id="{{effect.id}}"><i class="fa-solid fa-pen-to-square"></i></a>
<a data-action="removeEffect" data-effect="{{effect.id}}"><i class="fa-solid fa-trash"></i></a> <a data-action="deleteDoc" data-type="ActiveEffect" data-doc-id="{{effect.id}}"><i class="fa-solid fa-trash"></i></a>
</div> </div>
</div> </div>
{{/each}} {{/each}}