Merged with main

This commit is contained in:
WBHarry 2026-04-22 15:32:10 +02:00
commit 4e555dd314
174 changed files with 3707 additions and 1217 deletions

View file

@ -72,8 +72,8 @@ export default class ActionSelectionDialog extends HandlebarsApplicationMixin(Ap
static async #onChooseAction(event, button) {
const { actionId } = button.dataset;
this.#action = this.#item.system.actionsList.find(a => a._id === actionId);
Object.defineProperty(this.#event, 'shiftKey', {
this.action = this.item.system.actionsList.find(a => a._id === actionId);
Object.defineProperty(this.event, 'shiftKey', {
get() {
return event.shiftKey;
}

View file

@ -259,8 +259,9 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
const resetValue = increasing
? 0
: feature.system.resource.max
? Roll.replaceFormulaData(feature.system.resource.max, this.actor)
? new Roll(Roll.replaceFormulaData(feature.system.resource.max, this.actor)).evaluateSync().total
: 0;
await feature.update({ 'system.resource.value': resetValue });
}

View file

@ -115,6 +115,12 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
async _onRender(context, options) {
await super._onRender(context, options);
// if (this.element.querySelector('.roll-selection')) {
// for (const element of this.element.querySelectorAll('.team-member-container')) {
// element.classList.add('select-padding');
// }
// }
if (this.element.querySelector('.team-container')) return;
const initializationPart = this.element.querySelector('.initialization-container');
initializationPart.insertAdjacentHTML('afterend', '<div class="team-container"></div>');
@ -133,7 +139,10 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
context.members = {};
context.allHaveRolled = Object.keys(this.party.system.tagTeam.members).every(key => {
const data = this.party.system.tagTeam.members[key];
return Boolean(data.rollData);
const hasRolled = Boolean(data.rollData);
if (!hasRolled) return false;
return !data.rollData.options.hasDamage || Boolean(data.rollData.options.damage);
});
return context;

View file

@ -264,7 +264,9 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
key = event.target.closest('[data-key]').dataset.key;
if (!this.action[key]) return;
data[key].push(this.action.defaultValues[key] ?? {});
const value = key === 'areas' ? { name: this.action.item.name } : {};
data[key].push(this.action.defaultValues[key] ?? value);
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}

View file

@ -19,15 +19,17 @@ export default class DHActionConfig extends DHActionBaseConfig {
return context;
}
static async addEffect(_event) {
static async addEffect(event) {
const { areaIndex } = event.target.dataset;
if (!this.action.effects) return;
const data = this.action.toObject();
const created = await this.action.item.createEmbeddedDocuments('ActiveEffect', [
game.system.api.data.activeEffects.BaseEffect.getDefaultObject()
game.system.api.data.activeEffects.BaseEffect.getDefaultObject({ transfer: false })
]);
data.effects.push({ _id: created[0]._id });
if (areaIndex !== undefined) data.areas[areaIndex].effects.push(created[0]._id);
else data.effects.push({ _id: created[0]._id });
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
this.action.item.effects.get(created[0]._id).sheet.render(true);
}
@ -52,9 +54,19 @@ export default class DHActionConfig extends DHActionBaseConfig {
static removeEffect(event, button) {
if (!this.action.effects) return;
const index = button.dataset.index,
const { areaIndex, index } = button.dataset;
let effectId = null;
if (areaIndex !== undefined) {
effectId = this.action.areas[areaIndex].effects[index];
const data = this.action.toObject();
data.areas[areaIndex].effects.splice(index, 1);
this.constructor.updateForm.call(this, null, null, { object: foundry.utils.flattenObject(data) });
} else {
effectId = this.action.effects[index]._id;
this.constructor.removeElement.bind(this)(event, button);
this.constructor.removeElement.call(this, event, button);
}
this.action.item.deleteEmbeddedDocuments('ActiveEffect', [effectId]);
}

View file

@ -31,21 +31,35 @@ export default class DHActionSettingsConfig extends DHActionBaseConfig {
}
static async addEffect(_event) {
const { areaIndex } = event.target.dataset;
if (!this.action.effects) return;
const effectData = game.system.api.data.activeEffects.BaseEffect.getDefaultObject();
const effectData = game.system.api.data.activeEffects.BaseEffect.getDefaultObject({ transfer: false });
const data = this.action.toObject();
this.sheetUpdate(data, effectData);
this.effects = [...this.effects, effectData];
data.effects.push({ _id: effectData.id });
if (areaIndex !== undefined) data.areas[areaIndex].effects.push(effectData.id);
else data.effects.push({ _id: effectData.id });
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}
static removeEffect(event, button) {
if (!this.action.effects) return;
const index = button.dataset.index,
const { areaIndex, index } = button.dataset;
let effectId = null;
if (areaIndex !== undefined) {
effectId = this.action.areas[areaIndex].effects[index];
const data = this.action.toObject();
data.areas[areaIndex].effects.splice(index, 1);
this.constructor.updateForm.call(this, null, null, { object: foundry.utils.flattenObject(data) });
} else {
effectId = this.action.effects[index]._id;
this.constructor.removeElement.bind(this)(event, button);
this.constructor.removeElement.call(this, event, button);
}
this.sheetUpdate(
this.action.toObject(),
this.effects.find(x => x.id === effectId),

View file

@ -12,8 +12,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
static DEFAULT_OPTIONS = {
classes: ['character'],
position: { width: 850, height: 800 },
/* Foundry adds disabled to all buttons and inputs if editPermission is missing. This is not desired. */
editPermission: CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER,
actions: {
toggleVault: CharacterSheet.#toggleVault,
rollAttribute: CharacterSheet.#rollAttribute,
@ -68,7 +66,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
}
},
{
handler: CharacterSheet.#getEquipamentContextOptions,
handler: CharacterSheet.#getEquipmentContextOptions,
selector: '[data-item-uuid][data-type="armor"], [data-item-uuid][data-type="weapon"]',
options: {
parentClassHooks: false,
@ -170,6 +168,16 @@ export default class CharacterSheet extends DHBaseActorSheet {
return applicationOptions;
}
/** @inheritdoc */
_toggleDisabled(disabled) {
// Overriden to only disable text inputs by default.
// Everything else is done by checking @root.editable in the sheet
const form = this.form;
for (const input of form.querySelectorAll('input:not([type=search]), .editor.prosemirror')) {
input.disabled = disabled;
}
}
/** @inheritDoc */
async _onRender(context, options) {
await super._onRender(context, options);
@ -315,11 +323,11 @@ export default class CharacterSheet extends DHBaseActorSheet {
/**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */
const options = [
{
name: 'toLoadout',
label: 'toLoadout',
icon: 'fa-solid fa-arrow-up',
condition: target => {
visible: target => {
const doc = getDocFromElementSync(target);
return doc && doc.system.inVault;
return doc?.isOwner && doc.system.inVault;
},
callback: async target => {
const doc = await getDocFromElement(target);
@ -329,11 +337,11 @@ export default class CharacterSheet extends DHBaseActorSheet {
}
},
{
name: 'recall',
label: 'recall',
icon: 'fa-solid fa-bolt-lightning',
condition: target => {
visible: target => {
const doc = getDocFromElementSync(target);
return doc && doc.system.inVault;
return doc?.isOwner && doc.system.inVault;
},
callback: async (target, event) => {
const doc = await getDocFromElement(target);
@ -368,17 +376,17 @@ export default class CharacterSheet extends DHBaseActorSheet {
}
},
{
name: 'toVault',
label: 'toVault',
icon: 'fa-solid fa-arrow-down',
condition: target => {
visible: target => {
const doc = getDocFromElementSync(target);
return doc && !doc.system.inVault;
return doc?.isOwner && !doc.system.inVault;
},
callback: async target => (await getDocFromElement(target)).update({ 'system.inVault': true })
}
].map(option => ({
...option,
name: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.name}`,
label: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.label}`,
icon: `<i class="${option.icon}"></i>`
}));
@ -391,29 +399,29 @@ export default class CharacterSheet extends DHBaseActorSheet {
* @this {CharacterSheet}
* @protected
*/
static #getEquipamentContextOptions() {
static #getEquipmentContextOptions() {
const options = [
{
name: 'equip',
label: 'equip',
icon: 'fa-solid fa-hands',
condition: target => {
visible: target => {
const doc = getDocFromElementSync(target);
return doc && !doc.system.equipped;
return doc.isOwner && doc && !doc.system.equipped;
},
callback: (target, event) => CharacterSheet.#toggleEquipItem.call(this, event, target)
},
{
name: 'unequip',
label: 'unequip',
icon: 'fa-solid fa-hands',
condition: target => {
visible: target => {
const doc = getDocFromElementSync(target);
return doc && doc.system.equipped;
return doc.isOwner && doc && doc.system.equipped;
},
callback: (target, event) => CharacterSheet.#toggleEquipItem.call(this, event, target)
}
].map(option => ({
...option,
name: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.name}`,
label: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.label}`,
icon: `<i class="${option.icon}"></i>`
}));

View file

@ -433,18 +433,18 @@ export default function DHApplicationMixin(Base) {
/**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */
const options = [
{
name: 'disableEffect',
label: 'disableEffect',
icon: 'fa-solid fa-lightbulb',
condition: element => {
visible: element => {
const target = element.closest('[data-item-uuid]');
return !target.dataset.disabled && target.dataset.itemType !== 'beastform';
},
callback: async target => (await getDocFromElement(target)).update({ disabled: true })
},
{
name: 'enableEffect',
label: 'enableEffect',
icon: 'fa-regular fa-lightbulb',
condition: element => {
visible: element => {
const target = element.closest('[data-item-uuid]');
return target.dataset.disabled && target.dataset.itemType !== 'beastform';
},
@ -452,7 +452,7 @@ export default function DHApplicationMixin(Base) {
}
].map(option => ({
...option,
name: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.name}`,
label: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.label}`,
icon: `<i class="${option.icon}"></i>`
}));
@ -483,14 +483,14 @@ export default function DHApplicationMixin(Base) {
_getContextMenuCommonOptions({ usable = false, toChat = false, deletable = true }) {
const options = [
{
name: 'CONTROLS.CommonEdit',
label: 'CONTROLS.CommonEdit',
icon: 'fa-solid fa-pen-to-square',
condition: target => {
visible: target => {
const { dataset } = target.closest('[data-item-uuid]');
const doc = getDocFromElementSync(target);
return (
(!dataset.noCompendiumEdit && !doc) ||
(doc && (!doc?.hasOwnProperty('systemPath') || doc?.inCollection))
(doc?.isOwner && (!doc?.hasOwnProperty('systemPath') || doc?.inCollection))
);
},
callback: async target => (await getDocFromElement(target)).sheet.render({ force: true })
@ -499,14 +499,14 @@ export default function DHApplicationMixin(Base) {
if (usable) {
options.unshift({
name: 'DAGGERHEART.GENERAL.damage',
label: 'DAGGERHEART.GENERAL.damage',
icon: 'fa-solid fa-explosion',
condition: target => {
visible: target => {
const doc = getDocFromElementSync(target);
return (
const hasDamage =
!foundry.utils.isEmpty(doc?.system?.attack?.damage.parts) ||
!foundry.utils.isEmpty(doc?.damage?.parts)
);
!foundry.utils.isEmpty(doc?.damage?.parts);
return doc?.isOwner && hasDamage;
},
callback: async (target, event) => {
const doc = await getDocFromElement(target),
@ -522,11 +522,11 @@ export default function DHApplicationMixin(Base) {
});
options.unshift({
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
label: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
icon: 'fa-solid fa-burst',
condition: target => {
visible: target => {
const doc = getDocFromElementSync(target);
return doc && !(doc.type === 'domainCard' && doc.system.inVault);
return doc?.isOwner && !(doc.type === 'domainCard' && doc.system.inVault);
},
callback: async (target, event) => (await getDocFromElement(target)).use(event)
});
@ -534,18 +534,19 @@ export default function DHApplicationMixin(Base) {
if (toChat)
options.push({
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
label: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
icon: 'fa-solid fa-message',
callback: async target => (await getDocFromElement(target)).toChat(this.document.uuid)
});
if (deletable)
options.push({
name: 'CONTROLS.CommonDelete',
label: 'CONTROLS.CommonDelete',
icon: 'fa-solid fa-trash',
condition: element => {
visible: element => {
const target = element.closest('[data-item-uuid]');
return target.dataset.itemType !== 'beastform';
const doc = getDocFromElementSync(target);
return doc?.isOwner && target.dataset.itemType !== 'beastform';
},
callback: async (target, event) => {
const doc = await getDocFromElement(target);

View file

@ -31,7 +31,7 @@ export default class FeatureSheet extends DHBaseItemSheet {
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
}
};
//Might be wrong location but testing out if here is okay.
//Might be wrong location but testing out if here is okay.
/**@override */
async _prepareContext(options) {
const context = await super._prepareContext(options);

View file

@ -48,9 +48,9 @@ export default class DhActorDirectory extends foundry.applications.sidebar.tabs.
const options = super._getEntryContextOptions();
options.push(
{
name: 'DAGGERHEART.UI.Sidebar.actorDirectory.duplicateToNewTier',
label: 'DAGGERHEART.UI.Sidebar.actorDirectory.duplicateToNewTier',
icon: `<i class="fa-solid fa-arrow-trend-up" inert></i>`,
condition: li => {
visible: li => {
const actor = game.actors.get(li.dataset.entryId);
return actor?.type === 'adversary' && actor.system.type !== 'social';
},
@ -92,9 +92,9 @@ export default class DhActorDirectory extends foundry.applications.sidebar.tabs.
}
},
{
name: 'DAGGERHEART.UI.Sidebar.actorDirectory.activateParty',
label: 'DAGGERHEART.UI.Sidebar.actorDirectory.activateParty',
icon: `<i class="fa-regular fa-square"></i>`,
condition: li => {
visible: li => {
const actor = game.actors.get(li.dataset.entryId);
return actor && actor.type === 'party' && !actor.system.active;
},

View file

@ -103,23 +103,10 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
_getEntryContextOptions() {
return [
...super._getEntryContextOptions(),
// {
// name: 'Reroll',
// icon: '<i class="fa-solid fa-dice"></i>',
// condition: li => {
// const message = game.messages.get(li.dataset.messageId);
// return (game.user.isGM || message.isAuthor) && message.rolls.length > 0;
// },
// callback: li => {
// const message = game.messages.get(li.dataset.messageId);
// new game.system.api.applications.dialogs.RerollDialog(message).render({ force: true });
// }
// },
{
name: game.i18n.localize('DAGGERHEART.UI.ChatLog.rerollDamage'),
label: 'DAGGERHEART.UI.ChatLog.rerollDamage',
icon: '<i class="fa-solid fa-dice"></i>',
condition: li => {
visible: li => {
const message = game.messages.get(li.dataset.messageId);
const hasRolledDamage = message.system.hasDamage
? Object.keys(message.system.damage).length > 0

View file

@ -84,15 +84,15 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
_getCombatContextOptions() {
return [
{
name: 'COMBAT.ClearMovementHistories',
label: 'COMBAT.ClearMovementHistories',
icon: '<i class="fa-solid fa-shoe-prints"></i>',
condition: () => game.user.isGM && this.viewed?.combatants.size > 0,
visible: () => game.user.isGM && this.viewed?.combatants.size > 0,
callback: () => this.viewed.clearMovementHistories()
},
{
name: 'COMBAT.Delete',
label: 'COMBAT.Delete',
icon: '<i class="fa-solid fa-trash"></i>',
condition: () => game.user.isGM && !!this.viewed,
visible: () => game.user.isGM && !!this.viewed,
callback: () => this.viewed.endCombat()
}
];

View file

@ -6,8 +6,11 @@ export default class DHContextMenu extends foundry.applications.ux.ContextMenu {
static triggerContextMenu(event, altSelector) {
event.preventDefault();
event.stopPropagation();
const { clientX, clientY } = event;
const selector = altSelector ?? '[data-item-uuid]';
if (ui.context?.selector === selector) return;
const { clientX, clientY } = event;
const target = event.target.closest(selector) ?? event.currentTarget.closest(selector);
target?.dispatchEvent(
new PointerEvent('contextmenu', {