Merge branch 'main' into bug/103-enrich-htmlfield-content-before-its-used-in-applications

This commit is contained in:
Joaquin Pereyra 2025-07-14 15:49:20 -03:00
commit d26ed22e74
69 changed files with 1781 additions and 446 deletions

View file

@ -11,13 +11,16 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
this.setup = {
traits: this.character.system.traits,
ancestry: this.character.system.ancestry ?? {},
ancestryName: '',
mixedAncestry: false,
primaryAncestry: this.character.system.ancestry ?? {},
secondaryAncestry: {},
community: this.character.system.community ?? {},
class: this.character.system.class?.value ?? {},
subclass: this.character.system.class?.subclass ?? {},
experiences: {
[foundry.utils.randomID()]: { description: '', value: 2 },
[foundry.utils.randomID()]: { description: '', value: 2 }
[foundry.utils.randomID()]: { name: '', value: 2 },
[foundry.utils.randomID()]: { name: '', value: 2 }
},
domainCards: {
[foundry.utils.randomID()]: {},
@ -47,12 +50,13 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'dialog', 'dh-style', 'character-creation'],
position: { width: 800, height: 'auto' },
position: { width: 700, height: 'auto' },
actions: {
viewCompendium: this.viewCompendium,
viewItem: this.viewItem,
useSuggestedTraits: this.useSuggestedTraits,
equipmentChoice: this.equipmentChoice,
setupGoNext: this.setupGoNext,
finish: this.finish
},
form: {
@ -76,6 +80,12 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
static PARTS = {
tabs: { template: 'systems/daggerheart/templates/characterCreation/tabs.hbs' },
setup: { template: 'systems/daggerheart/templates/characterCreation/tabs/setup.hbs' },
ancestry: { template: 'systems/daggerheart/templates/characterCreation/setupTabs/ancestry.hbs' },
community: { template: 'systems/daggerheart/templates/characterCreation/setupTabs/community.hbs' },
class: { template: 'systems/daggerheart/templates/characterCreation/setupTabs/class.hbs' },
traits: { template: 'systems/daggerheart/templates/characterCreation/setupTabs/traits.hbs' },
experience: { template: 'systems/daggerheart/templates/characterCreation/setupTabs/experience.hbs' },
domainCards: { template: 'systems/daggerheart/templates/characterCreation/setupTabs/domainCards.hbs' },
equipment: { template: 'systems/daggerheart/templates/characterCreation/tabs/equipment.hbs' },
// story: { template: 'systems/daggerheart/templates/characterCreation/tabs/story.hbs' },
footer: { template: 'systems/daggerheart/templates/characterCreation/footer.hbs' }
@ -107,6 +117,51 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
// }
};
static SETUPTABS = {
ancestry: {
active: true,
cssClass: '',
group: 'setup',
id: 'ancestry',
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.setupTabs.ancestry'
},
community: {
active: false,
cssClass: '',
group: 'setup',
id: 'community',
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.setupTabs.community'
},
class: {
active: false,
cssClass: '',
group: 'setup',
id: 'class',
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.setupTabs.class'
},
traits: {
active: false,
cssClass: '',
group: 'setup',
id: 'traits',
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.setupTabs.traits'
},
experience: {
active: false,
cssClass: '',
group: 'setup',
id: 'experience',
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.setupTabs.experience'
},
domainCards: {
active: false,
cssClass: '',
group: 'setup',
id: 'domainCards',
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.setupTabs.domainCards'
}
};
_getTabs(tabs) {
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active;
@ -114,14 +169,16 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
switch (v.id) {
case 'setup':
const ancestryFinished = this.setup.primaryAncestry.uuid;
const communityFinished = this.setup.community.uuid;
const classFinished = this.setup.class.uuid && this.setup.subclass.uuid;
const heritageFinished = this.setup.ancestry.uuid && this.setup.community.uuid;
const traitsFinished = Object.values(this.setup.traits).every(x => x.value !== null);
const experiencesFinished = Object.values(this.setup.experiences).every(x => x.description);
const experiencesFinished = Object.values(this.setup.experiences).every(x => x.name);
const domainCardsFinished = Object.values(this.setup.domainCards).every(x => x.uuid);
v.finished =
ancestryFinished &&
communityFinished &&
classFinished &&
heritageFinished &&
traitsFinished &&
experiencesFinished &&
domainCardsFinished;
@ -146,15 +203,44 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
return tabs;
}
_getSetupTabs(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' : '';
switch (v.id) {
case 'community':
v.disabled = this.setup.visibility < 2;
break;
case 'class':
v.disabled = this.setup.visibility < 3;
break;
case 'traits':
v.disabled = this.setup.visibility < 4;
break;
case 'experience':
v.disabled = this.setup.visibility < 5;
break;
case 'domainCards':
v.disabled = this.setup.visibility < 6;
break;
}
}
return tabs;
}
changeTab(tab, group, options) {
super.changeTab(tab, group, options);
for (var listTab of Object.keys(this.constructor.TABS)) {
const marker = options.navElement.querySelector(`a[data-action="tab"].${listTab} .finish-marker`);
if (listTab === tab) {
marker.classList.add('active');
} else {
marker.classList.remove('active');
if (group === 'primary') {
for (var listTab of Object.keys(this.constructor.TABS)) {
const marker = options.navElement.querySelector(`a[data-action="tab"].${listTab} .finish-marker`);
if (listTab === tab) {
marker.classList.add('active');
} else {
marker.classList.remove('active');
}
}
}
}
@ -163,6 +249,11 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
super._attachPartListeners(partId, htmlElement, options);
this._dragDrop.forEach(d => d.bind(htmlElement));
htmlElement.querySelectorAll('.mixed-ancestry-slider').forEach(element => {
element.addEventListener('input', this.mixedAncestryToggle.bind(this));
element.addEventListener('click', this.mixedAncestryToggle.bind(this));
});
}
async _prepareContext(_options) {
@ -174,7 +265,30 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
async _preparePartContext(partId, context) {
switch (partId) {
case 'footer':
context.isLastTab = this.tabGroups.setup === 'domainCards';
switch (this.tabGroups.setup) {
case null:
case 'ancestry':
context.nextDisabled = this.setup.visibility === 1;
break;
case 'community':
context.nextDisabled = this.setup.visibility === 2;
break;
case 'class':
context.nextDisabled = this.setup.visibility === 3;
break;
case 'traits':
context.nextDisabled = this.setup.visibility === 4;
break;
case 'experience':
context.nextDisabled = this.setup.visibility === 5;
break;
}
break;
case 'setup':
context.setupTabs = this._getSetupTabs(this.constructor.SETUPTABS);
const availableTraitModifiers = game.settings
.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew)
.traitArray.map(trait => ({ key: trait, name: trait }));
@ -215,13 +329,13 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
context.experience = {
values: this.setup.experiences,
nrTotal: Object.keys(this.setup.experiences).length,
nrSelected: Object.values(this.setup.experiences).reduce(
(acc, exp) => acc + (exp.description ? 1 : 0),
0
)
nrSelected: Object.values(this.setup.experiences).reduce((acc, exp) => acc + (exp.name ? 1 : 0), 0)
};
context.ancestry = { ...this.setup.ancestry, compendium: 'ancestries' };
context.mixedAncestry = Number(this.setup.mixedAncestry);
context.ancestryName = this.setup.ancestryName;
context.primaryAncestry = { ...this.setup.primaryAncestry, compendium: 'ancestries' };
context.secondaryAncestry = { ...this.setup.secondaryAncestry, compendium: 'ancestries' };
context.community = { ...this.setup.community, compendium: 'communities' };
context.class = { ...this.setup.class, compendium: 'classes' };
context.subclass = { ...this.setup.subclass, compendium: 'subclasses' };
@ -278,18 +392,29 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
this.render();
}
mixedAncestryToggle(event) {
event.preventDefault();
event.stopPropagation();
this.setup.mixedAncestry = !this.setup.mixedAncestry;
if (!this.setup.mixedAncestry) this.setup.secondaryAncestry = {};
this.render();
}
getUpdateVisibility() {
switch (this.setup.visibility) {
case 6:
return 6;
case 5:
return 5;
return Object.values(this.setup.experiences).every(x => x.name) ? 6 : 5;
case 4:
return Object.values(this.setup.experiences).every(x => x.description) ? 5 : 4;
return Object.values(this.setup.traits).every(x => x.value !== null) ? 5 : 4;
case 3:
return Object.values(this.setup.traits).every(x => x.value !== null) ? 4 : 3;
return this.setup.class.uuid && this.setup.subclass.uuid ? 4 : 3;
case 2:
return this.setup.ancestry.uuid && this.setup.community.uuid ? 3 : 2;
return this.setup.community.uuid ? 3 : 2;
case 1:
return this.setup.class.uuid && this.setup.subclass.uuid ? 2 : 1;
return this.setup.primaryAncestry.uuid ? 2 : 1;
}
}
@ -348,8 +473,46 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
this.render();
}
static setupGoNext() {
switch (this.setup.visibility) {
case 2:
this.tabGroups.setup = 'community';
break;
case 3:
this.tabGroups.setup = 'class';
break;
case 4:
this.tabGroups.setup = 'traits';
break;
case 5:
this.tabGroups.setup = 'experience';
break;
case 6:
this.tabGroups.setup = 'domainCards';
break;
}
this.render();
}
static async finish() {
await this.character.createEmbeddedDocuments('Item', [this.setup.ancestry]);
const primaryAncestryFeature = this.setup.primaryAncestry.system.primaryFeature;
const secondaryAncestryFeature = this.setup.secondaryAncestry?.uuid
? this.setup.secondaryAncestry.system.secondaryFeature
: this.setup.primaryAncestry.system.secondaryFeature;
const ancestry = {
...this.setup.primaryAncestry,
name: this.setup.ancestryName ?? this.setup.primaryAncestry.name,
system: {
...this.setup.primaryAncestry.system,
features: [primaryAncestryFeature.uuid, secondaryAncestryFeature.uuid],
primaryFeature: primaryAncestryFeature.uuid,
secondaryFeature: secondaryAncestryFeature.uuid
}
};
await this.character.createEmbeddedDocuments('Item', [ancestry]);
await this.character.createEmbeddedDocuments('Item', [this.setup.community]);
await this.character.createEmbeddedDocuments('Item', [this.setup.class]);
await this.character.createEmbeddedDocuments('Item', [this.setup.subclass]);
@ -396,8 +559,15 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
async _onDrop(event) {
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
const item = await foundry.utils.fromUuid(data.uuid);
if (item.type === 'ancestry' && event.target.closest('.ancestry-card')) {
this.setup.ancestry = {
if (item.type === 'ancestry' && event.target.closest('.primary-ancestry-card')) {
this.setup.ancestryName = item.name;
this.setup.primaryAncestry = {
...item,
effects: Array.from(item.effects).map(x => x.toObject()),
uuid: item.uuid
};
} else if (item.type === 'ancestry' && event.target.closest('.secondary-ancestry-card')) {
this.setup.secondaryAncestry = {
...item,
effects: Array.from(item.effects).map(x => x.toObject()),
uuid: item.uuid

View file

@ -7,3 +7,4 @@ export { default as DamageSelectionDialog } from './damageSelectionDialog.mjs';
export { default as DeathMove } from './deathMove.mjs';
export { default as Downtime } from './downtime.mjs';
export { default as OwnershipSelection } from './ownershipSelection.mjs';
export { default as ResourceDiceDialog } from './resourceDiceDialog.mjs';

View file

@ -66,7 +66,12 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
context.canRoll = true;
if (this.config.costs?.length) {
const updatedCosts = this.action.calcCosts(this.config.costs);
context.costs = updatedCosts;
context.costs = updatedCosts.map(x => ({
...x,
label: x.keyIsID
? this.action.parent.parent.name
: game.i18n.localize(CONFIG.DH.GENERAL.abilityCosts[x.key].label)
}));
context.canRoll = this.action.hasCost(updatedCosts);
this.config.data.scale = this.config.costs[0].total;
}
@ -74,7 +79,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
context.uses = this.action.calcUses(this.config.uses);
context.canRoll = context.canRoll && this.action.hasUses(context.uses);
}
if(this.roll) {
if (this.roll) {
context.roll = this.roll;
context.rollType = this.roll?.constructor.name;
context.experiences = Object.keys(this.config.data.experiences).map(id => ({

View file

@ -0,0 +1,99 @@
import { itemAbleRollParse } from '../../helpers/utils.mjs';
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
export default class ResourceDiceDialog extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(item, actor, options = {}) {
super(options);
this.item = item;
this.actor = actor;
this.diceStates = foundry.utils.deepClone(item.system.resource.diceStates);
}
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'resource-dice'],
window: {
icon: 'fa-solid fa-dice'
},
actions: {
rerollDice: this.rerollDice,
save: this.save
},
form: {
handler: this.updateResourceDice,
submitOnChange: true,
submitOnClose: false
}
};
/** @override */
static PARTS = {
resourceDice: {
id: 'resourceDice',
template: 'systems/daggerheart/templates/dialogs/dice-roll/resourceDice.hbs'
}
};
get title() {
return game.i18n.format('DAGGERHEART.APPLICATIONS.ResourceDice.title', { name: this.item.name });
}
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.item = this.item;
context.actor = this.actor;
context.diceStates = this.diceStates;
return context;
}
static async updateResourceDice(event, _, formData) {
const { diceStates } = foundry.utils.expandObject(formData.object);
this.diceStates = Object.keys(diceStates).reduce((acc, key) => {
const resourceState = this.item.system.resource.diceStates[key];
acc[key] = { ...diceStates[key], used: Boolean(resourceState?.used) };
return acc;
}, {});
this.render();
}
static async save() {
this.rollValues = Object.values(this.diceStates);
this.close();
}
static async rerollDice() {
const max = itemAbleRollParse(this.item.system.resource.max, this.actor, this.item);
const diceFormula = `${max}d${this.item.system.resource.dieFaces}`;
const roll = await new Roll(diceFormula).evaluate();
if (game.modules.get('dice-so-nice')?.active) await game.dice3d.showForRoll(roll, game.user, true);
this.rollValues = roll.terms[0].results.map(x => ({ value: x.result, used: false }));
this.resetUsed = true;
const cls = getDocumentClass('ChatMessage');
const msg = new cls({
user: game.user.id,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/ui/chat/resource-roll.hbs',
{
user: this.actor.name,
name: this.item.name
}
)
});
cls.create(msg.toObject());
this.close();
}
static async create(item, actor, options = {}) {
return new Promise(resolve => {
const app = new this(item, actor, options);
app.addEventListener('close', () => resolve(app.rollValues), { once: true });
app.render({ force: true });
});
}
}

View file

@ -107,6 +107,7 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
context.hasBaseDamage = !!this.action.parent.attack;
context.getRealIndex = this.getRealIndex.bind(this);
context.getEffectDetails = this.getEffectDetails.bind(this);
context.costOptions = this.getCostOptions();
context.disableOption = this.disableOption.bind(this);
context.isNPC = this.action.actor && this.action.actor.type !== 'character';
context.hasRoll = this.action.hasRoll;
@ -125,8 +126,21 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
this.render(true);
}
disableOption(index, options, choices) {
const filtered = foundry.utils.deepClone(options);
getCostOptions() {
const options = foundry.utils.deepClone(CONFIG.DH.GENERAL.abilityCosts);
const resource = this.action.parent.resource;
if (resource) {
options[this.action.parent.parent.id] = {
label: this.action.parent.parent.name,
group: 'TYPES.Actor.character'
};
}
return options;
}
disableOption(index, costOptions, choices) {
const filtered = foundry.utils.deepClone(costOptions);
Object.keys(filtered).forEach(o => {
if (choices.find((c, idx) => c.type === o && index !== idx)) filtered[o].disabled = true;
});
@ -142,11 +156,19 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
return this.action.item.effects.get(id);
}
_prepareSubmitData(event, formData) {
_prepareSubmitData(_event, formData) {
const submitData = foundry.utils.expandObject(formData.object);
for (const keyPath of this.constructor.CLEAN_ARRAYS) {
const data = foundry.utils.getProperty(submitData, keyPath);
if (data) foundry.utils.setProperty(submitData, keyPath, Object.values(data));
const dataValues = data ? Object.values(data) : [];
if (keyPath === 'cost') {
for (var value of dataValues) {
const item = this.action.parent.parent.id === value.key;
value.keyIsID = Boolean(item);
}
}
if (data) foundry.utils.setProperty(submitData, keyPath, dataValues);
}
return submitData;
}

View file

@ -4,7 +4,7 @@ import { abilities } from '../../../config/actorConfig.mjs';
import DhCharacterlevelUp from '../../levelup/characterLevelup.mjs';
import DhCharacterCreation from '../../characterCreation/characterCreation.mjs';
import FilterMenu from '../../ux/filter-menu.mjs';
import { getDocFromElement } from "../../../helpers/utils.mjs";
import { getDocFromElement, itemAbleRollParse } from "../../../helpers/utils.mjs";
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
@ -23,6 +23,11 @@ export default class CharacterSheet extends DHBaseActorSheet {
makeDeathMove: CharacterSheet.#makeDeathMove,
levelManagement: CharacterSheet.#levelManagement,
toggleEquipItem: CharacterSheet.#toggleEquipItem,
useItem: this.useItem, //TODO Fix this
useAction: this.useAction,
toggleResourceDice: this.toggleResourceDice,
handleResourceDice: this.handleResourceDice,
toChat: this.toChat
},
window: {
resizable: true
@ -102,6 +107,17 @@ export default class CharacterSheet extends DHBaseActorSheet {
}
};
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
htmlElement.querySelectorAll('.inventory-item-resource').forEach(element => {
element.addEventListener('change', this.updateItemResource.bind(this));
});
htmlElement.querySelectorAll('.inventory-item-quantity').forEach(element => {
element.addEventListener('change', this.updateItemQuantity.bind(this));
});
}
/** @inheritDoc */
async _onRender(context, options) {
await super._onRender(context, options);
@ -486,6 +502,27 @@ export default class CharacterSheet extends DHBaseActorSheet {
}
}
/* -------------------------------------------- */
/* Application Listener Actions */
/* -------------------------------------------- */
async updateItemResource(event) {
const item = this.getItem(event.currentTarget);
if (!item) return;
const max = item.system.resource.max ? itemAbleRollParse(item.system.resource.max, this.document, item) : null;
const value = max ? Math.min(Number(event.currentTarget.value), max) : event.currentTarget.value;
await item.update({ 'system.resource.value': value });
this.render();
}
async updateItemQuantity(event) {
const item = this.getItem(event.currentTarget);
if (!item) return;
await item.update({ 'system.quantity': event.currentTarget.value });
this.render();
}
/* -------------------------------------------- */
/* Application Clicks Actions */
/* -------------------------------------------- */
@ -639,16 +676,81 @@ export default class CharacterSheet extends DHBaseActorSheet {
action.use(event);
}
/**
* Toggle the used state of a resource dice.
* @type {ApplicationClickAction}
*/
static async toggleResourceDice(event) {
const target = event.target.closest('.item-resource');
const item = this.getItem(event);
if (!item) return;
const diceState = item.system.resource.diceStates[target.dataset.dice];
await item.update({
[`system.resource.diceStates.${target.dataset.dice}.used`]: diceState?.used ? !diceState.used : true
});
}
/**
* Handle the roll values of resource dice.
* @type {ApplicationClickAction}
*/
static async handleResourceDice(event) {
const item = this.getItem(event);
if (!item) return;
const rollValues = await game.system.api.applications.dialogs.ResourceDiceDialog.create(item, this.document);
if (!rollValues) return;
await item.update({
'system.resource.diceStates': rollValues.reduce((acc, state, index) => {
acc[index] = { value: state.value, used: state.used };
return acc;
}, {})
});
this.render();
}
/**
* Send item to Chat
* @type {ApplicationClickAction}
*/
static async toChat(event, button) {
if (button?.dataset?.type === 'experience') {
const experience = this.document.system.experiences[button.dataset.uuid];
const cls = getDocumentClass('ChatMessage');
const systemData = {
name: game.i18n.localize('DAGGERHEART.GENERAL.Experience.single'),
description: `${experience.name} ${experience.value.signedString()}`
};
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());
} else {
const item = this.getItem(event);
if (!item) return;
item.toChat(this.document.id);
}
}
async _onDragStart(event) {
const item = this.getItem(event);
const dragData = {
type: item.documentName,
uuid: item.uuid
};
event.dataTransfer.setData('text/plain', JSON.stringify(dragData));
super._onDragStart(event);
}
@ -656,7 +758,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
// Prevent event bubbling to avoid duplicate handling
event.preventDefault();
event.stopPropagation();
super._onDrop(event);
this._onDropItem(event, TextEditor.getDragEventData(event));
}

View file

@ -21,6 +21,11 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
},
actions: {
removeAction: DHBaseItemSheet.#removeAction,
addFeature: DHBaseItemSheet.#addFeature,
editFeature: DHBaseItemSheet.#editFeature,
removeFeature: DHBaseItemSheet.#removeFeature,
addResource: DHBaseItemSheet.#addResource,
removeResource: DHBaseItemSheet.#removeResource
addFeature: DHBaseItemSheet.#addFeature
},
dragDrop: [
@ -179,6 +184,71 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
'system.features': [...this.document.system.features, feature]
});
}
/**
* Edit an existing feature on the item
* @type {ApplicationClickAction}
*/
static async #editFeature(_event, button) {
const target = button.closest('.feature-item');
const feature = this.document.system.features.find(x => x?.id === target.id);
if (!feature) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
return;
}
feature.sheet.render(true);
}
/**
* Remove a feature from the item.
* @type {ApplicationClickAction}
*/
static async #removeFeature(event, button) {
event.stopPropagation();
const target = button.closest('.feature-item');
const feature = this.document.system.features.find(x => x && x.id === target.id);
if (feature) {
const confirmed = await foundry.applications.api.DialogV2.confirm({
window: {
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
type: game.i18n.localize(`TYPES.Item.feature`),
name: feature.name
})
},
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: feature.name })
});
if (!confirmed) return;
}
await this.document.update({
'system.features': this.document.system.features
.filter(feature => feature && feature.id !== target.id)
.map(x => x.uuid)
});
}
/**
* Add a resource to the item.
* @type {ApplicationClickAction}
*/
static async #addResource() {
await this.document.update({
'system.resource': { type: 'simple', value: 0 }
});
}
/**
* Remove the resource from the item.
* @type {ApplicationClickAction}
*/
static async #removeResource() {
await this.document.update({
'system.resource': null
});
}
/* -------------------------------------------- */
/* Application Drag/Drop */
/* -------------------------------------------- */
@ -193,7 +263,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
if (featureItem) {
const feature = this.document.system.features.find(x => x?.id === featureItem.id);
if (!feature) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.notifications.featureIsMissing'));
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
return;
}

View file

@ -10,10 +10,6 @@ export default class DHHeritageSheet extends DHBaseItemSheet {
static PARTS = {
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' },
feature: {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-features.hbs',
scrollable: ['.feature']
},
effects: {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
scrollable: ['.effects']

View file

@ -1,4 +1,3 @@
export default function ItemAttachmentSheet(Base) {
return class extends Base {
static DEFAULT_OPTIONS = {
@ -25,10 +24,7 @@ export default function ItemAttachmentSheet(Base) {
...super.TABS,
primary: {
...super.TABS?.primary,
tabs: [
...(super.TABS?.primary?.tabs || []),
{ id: 'attachments' }
],
tabs: [...(super.TABS?.primary?.tabs || []), { id: 'attachments' }],
initial: super.TABS?.primary?.initial || 'description',
labelPrefix: super.TABS?.primary?.labelPrefix || 'DAGGERHEART.GENERAL.Tabs'
}
@ -46,29 +42,28 @@ export default function ItemAttachmentSheet(Base) {
async _onDrop(event) {
const data = TextEditor.getDragEventData(event);
const attachmentsSection = event.target.closest('.attachments-section');
if (!attachmentsSection) return super._onDrop(event);
event.preventDefault();
event.stopPropagation();
const item = await Item.implementation.fromDropData(data);
if (!item) return;
// Call the data model's public method
await this.document.system.addAttachment(item);
}
static async #removeAttachment(event, target) {
// Call the data model's public method
await this.document.system.removeAttachment(target.dataset.uuid);
}
}
async _preparePartContext(partId, context) {
await super._preparePartContext(partId, context);
if (partId === 'attachments') {
// Keep this simple UI preparation in the mixin
const attachedUUIDs = this.document.system.attached;
@ -83,8 +78,8 @@ export default function ItemAttachmentSheet(Base) {
})
);
}
return context;
}
};
}
}

View file

@ -3,12 +3,109 @@ import DHHeritageSheet from '../api/heritage-sheet.mjs';
export default class AncestrySheet extends DHHeritageSheet {
/**@inheritdoc */
static DEFAULT_OPTIONS = {
classes: ['ancestry']
classes: ['ancestry'],
actions: {
addFeature: AncestrySheet.#addFeature,
editFeature: AncestrySheet.#editFeature,
removeFeature: AncestrySheet.#removeFeature
},
dragDrop: [{ dragSelector: null, dropSelector: '.tab.features .drop-section' }]
};
/**@inheritdoc */
static PARTS = {
header: { template: 'systems/daggerheart/templates/sheets/items/ancestry/header.hbs' },
...super.PARTS
...super.PARTS,
features: { template: 'systems/daggerheart/templates/sheets/items/ancestry/features.hbs' }
};
/* -------------------------------------------- */
/* Application Clicks Actions */
/* -------------------------------------------- */
/**
* Add a new feature to the item, prompting the user for its type.
* @type {ApplicationClickAction}
*/
static async #addFeature(_event, button) {
const feature = await game.items.documentClass.create({
type: 'feature',
name: game.i18n.format('DOCUMENT.New', { type: game.i18n.localize('TYPES.Item.feature') }),
system: {
subType: button.dataset.type
}
});
await this.document.update({
'system.features': [...this.document.system.features.map(x => x.uuid), feature.uuid]
});
}
/**
* Edit an existing feature on the item
* @type {ApplicationClickAction}
*/
static async #editFeature(_event, button) {
const target = button.closest('.feature-item');
const feature = this.document.system[`${target.dataset.type}Feature`];
if (!feature || Object.keys(feature).length === 0) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
return;
}
feature.sheet.render(true);
}
/**
* Remove a feature from the item.
* @type {ApplicationClickAction}
*/
static async #removeFeature(event, button) {
event.stopPropagation();
const target = button.closest('.feature-item');
const feature = this.document.system[`${target.dataset.type}Feature`];
const featureExists = feature && Object.keys(feature).length > 0;
if (featureExists) {
const confirmed = await foundry.applications.api.DialogV2.confirm({
window: {
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
type: game.i18n.localize(`TYPES.Item.feature`),
name: feature.name
})
},
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: feature.name })
});
if (!confirmed) return;
}
if (featureExists && target.dataset.type === 'primary') await feature.update({ 'system.primary': null });
await this.document.update({
'system.features': this.document.system.features.filter(x => x && x.uuid !== feature.uuid).map(x => x.uuid)
});
}
/* -------------------------------------------- */
/* Application Drag/Drop */
/* -------------------------------------------- */
/**
* On drop on the item.
* @param {DragEvent} event - The drag event
*/
async _onDrop(event) {
event.stopPropagation();
event.preventDefault();
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
const item = await fromUuid(data.uuid);
if (item?.type === 'feature') {
const subType = event.target.closest('.primary-feature') ? 'primary' : 'secondary';
await item.update({ 'system.subType': subType });
await this.document.update({
'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid]
});
}
}
}

View file

@ -87,6 +87,16 @@ export default class ClassSheet extends DHBaseItemSheet {
await this.document.update({
'system.subclasses': [...this.document.system.subclasses.map(x => x.uuid), item.uuid]
});
} else if (item.type === 'feature') {
if (target.classList.contains('hope-feature')) {
await this.document.update({
'system.hopeFeatures': [...this.document.system.hopeFeatures.map(x => x.uuid), item.uuid]
});
} else if (target.classList.contains('class-feature')) {
await this.document.update({
'system.classFeatures': [...this.document.system.classFeatures.map(x => x.uuid), item.uuid]
});
}
} else if (item.type === 'weapon') {
if (target.classList.contains('primary-weapon-section')) {
if (!this.document.system.characterGuide.suggestedPrimaryWeapon && !item.system.secondary)
@ -146,7 +156,7 @@ export default class ClassSheet extends DHBaseItemSheet {
static async #removeItemFromCollection(_event, element) {
const { uuid, target } = element.dataset;
const prop = foundry.utils.getProperty(this.document.system, target);
await this.document.update({ [target]: prop.filter(i => i.uuid !== uuid) });
await this.document.update({ [`system.${target}`]: prop.filter(i => i.uuid !== uuid) });
}
/**

View file

@ -9,6 +9,10 @@ export default class CommunitySheet extends DHHeritageSheet {
/**@inheritdoc */
static PARTS = {
header: { template: 'systems/daggerheart/templates/sheets/items/community/header.hbs' },
...super.PARTS
...super.PARTS,
feature: {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-features.hbs',
scrollable: ['.feature']
}
};
}

View file

@ -13,6 +13,7 @@ export default class FeatureSheet extends DHBaseItemSheet {
header: { template: 'systems/daggerheart/templates/sheets/items/feature/header.hbs' },
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' },
settings: { template: 'systems/daggerheart/templates/sheets/items/feature/settings.hbs' },
actions: {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-actions.hbs',
scrollable: ['.actions']
@ -26,7 +27,7 @@ export default class FeatureSheet extends DHBaseItemSheet {
/**@override */
static TABS = {
primary: {
tabs: [{ id: 'description' }, { id: 'actions' }, { id: 'effects' }],
tabs: [{ id: 'description' }, { id: 'settings' }, { id: 'actions' }, { id: 'effects' }],
initial: 'description',
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
}

View file

@ -64,7 +64,7 @@ export default class SubclassSheet extends DHBaseItemSheet {
if (featureItem) {
const feature = this.document.system[featureItem.dataset.type];
if (!feature) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.notifications.featureIsMissing'));
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
return;
}

View file

@ -1,6 +1,6 @@
export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog {
constructor() {
super();
constructor(options) {
super(options);
this.targetTemplate = {
activeLayer: undefined,
@ -14,6 +14,8 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
}
addChatListeners = async (app, html, data) => {
super.addChatListeners(app, html, data);
html.querySelectorAll('.duality-action-damage').forEach(element =>
element.addEventListener('click', event => this.onRollDamage(event, data.message))
);