Merge branch 'main' into feature/death-moves

This commit is contained in:
Chris Ryan 2025-12-23 11:17:37 +10:00
commit 9a3355175b
229 changed files with 2452 additions and 893 deletions

View file

@ -195,9 +195,9 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
if (this.config.roll) {
this.reactionOverride = !this.reactionOverride;
this.config.actionType = this.reactionOverride
? CONFIG.DH.ITEM.actionTypes.reaction.id
: this.config.actionType === CONFIG.DH.ITEM.actionTypes.reaction.id
? CONFIG.DH.ITEM.actionTypes.action.id
? 'reaction'
: this.config.actionType === 'reaction'
? 'action'
: this.config.actionType;
this.render();
}

View file

@ -93,7 +93,7 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
}
getRefreshables() {
const actionItems = this.actor.items.reduce((acc, x) => {
const actionItems = this.actor.items.filter(x => this.actor.system.isItemAvailable(x)).reduce((acc, x) => {
if (x.system.actions) {
const recoverable = x.system.actions.reduce((acc, action) => {
if (refreshIsAllowed([this.shortrest ? 'shortRest' : 'longRest'], action.uses.recovery)) {

View file

@ -1,69 +1,61 @@
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class ItemTransferDialog extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(item) {
constructor(data) {
super({});
this.item = item;
this.quantity = item.system.quantity;
this.data = data;
}
get title() {
return this.item.name;
return this.data.title;
}
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'dh-style', 'dialog', 'item-transfer'],
position: { width: 300, height: 'auto' },
position: { width: 400, height: 'auto' },
window: { icon: 'fa-solid fa-hand-holding-hand' },
actions: {
finish: ItemTransferDialog.#finish
},
form: { handler: this.updateData, submitOnChange: true, closeOnSubmit: false }
}
};
static PARTS = {
main: { template: 'systems/daggerheart/templates/dialogs/item-transfer.hbs' }
main: { template: 'systems/daggerheart/templates/dialogs/item-transfer.hbs', root: true }
};
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
htmlElement.querySelector('.number-display').addEventListener('change', event => {
this.quantity = isNaN(event.target.value) ? this.quantity : Number(event.target.value);
this.render();
});
}
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.item = this.item;
context.quantity = this.quantity;
return context;
}
static async updateData(_event, _element, formData) {
const { quantity } = foundry.utils.expandObject(formData.object);
this.quantity = quantity;
this.render();
return foundry.utils.mergeObject(context, this.data);
}
static async #finish() {
this.close({ submitted: true });
this.selected = this.form.elements.quantity.valueAsNumber || null;
this.close();
}
close(options = {}) {
if (!options.submitted) this.quantity = null;
static #determineTransferOptions({ originActor, targetActor, item, currency }) {
originActor ??= item?.actor;
const homebrewKey = CONFIG.DH.SETTINGS.gameSettings.Homebrew;
const currencySetting = game.settings.get(CONFIG.DH.id, homebrewKey).currency?.[currency] ?? null;
super.close();
return {
originActor,
targetActor,
itemImage: item?.img,
currencyIcon: currencySetting?.icon,
max: item?.system.quantity ?? originActor.system.gold[currency] ?? 0,
title: item?.name ?? currencySetting?.label
};
}
static async configure(item) {
static async configure(options) {
return new Promise(resolve => {
const app = new this(item);
app.addEventListener('close', () => resolve(app.quantity), { once: true });
const data = this.#determineTransferOptions(options);
if (data.max <= 1) return resolve(data.max);
const app = new this(data);
app.addEventListener('close', () => resolve(app.selected), { once: true });
app.render({ force: true });
});
}

View file

@ -220,8 +220,8 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
!roll.system.isCritical && criticalRoll
? (await getCritDamageBonus(damage.formula)) + damage.total
: damage.total;
const updatedDamageParts = damage.parts;
if (systemData.damage[key]) {
const updatedDamageParts = damage.parts;
if (!roll.system.isCritical && criticalRoll) {
for (let part of updatedDamageParts) {
const criticalDamage = await getCritDamageBonus(part.formula);

View file

@ -44,6 +44,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
deleteAdversaryType: this.deleteAdversaryType,
selectAdversaryType: this.selectAdversaryType,
save: this.save,
resetTokenSizes: this.resetTokenSizes,
reset: this.reset
},
form: { handler: this.updateData, submitOnChange: true }
@ -424,6 +425,14 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
this.close();
}
static async resetTokenSizes() {
await this.settings.updateSource({
tokenSizes: this.settings.schema.fields.tokenSizes.initial
});
this.render();
}
static async reset() {
const confirmed = await foundry.applications.api.DialogV2.confirm({
window: {

View file

@ -1,4 +1,18 @@
export default class DhPrototypeTokenConfig extends foundry.applications.sheets.PrototypeTokenConfig {
/** @override */
static PARTS = {
tabs: super.PARTS.tabs,
identity: super.PARTS.identity,
appearance: {
template: 'systems/daggerheart/templates/sheets-settings/token-config/appearance.hbs',
scrollable: ['']
},
vision: super.PARTS.vision,
light: super.PARTS.light,
resources: super.PARTS.resources,
footer: super.PARTS.footer
};
/** @inheritDoc */
async _prepareResourcesTab() {
const token = this.token;
@ -17,4 +31,11 @@ export default class DhPrototypeTokenConfig extends foundry.applications.sheets.
turnMarkerAnimations: CONFIG.Combat.settings.turnMarkerAnimations
};
}
async _prepareAppearanceTab() {
const context = await super._prepareAppearanceTab();
context.actorSizeUsed = this.token.actor ? Boolean(this.token.actor.system.size) : false;
return context;
}
}

View file

@ -1,4 +1,18 @@
export default class DhTokenConfig extends foundry.applications.sheets.TokenConfig {
/** @override */
static PARTS = {
tabs: super.PARTS.tabs,
identity: super.PARTS.identity,
appearance: {
template: 'systems/daggerheart/templates/sheets-settings/token-config/appearance.hbs',
scrollable: ['']
},
vision: super.PARTS.vision,
light: super.PARTS.light,
resources: super.PARTS.resources,
footer: super.PARTS.footer
};
/** @inheritDoc */
async _prepareResourcesTab() {
const token = this.token;
@ -17,4 +31,11 @@ export default class DhTokenConfig extends foundry.applications.sheets.TokenConf
turnMarkerAnimations: CONFIG.Combat.settings.turnMarkerAnimations
};
}
async _prepareAppearanceTab() {
const context = await super._prepareAppearanceTab();
context.actorSizeUsed = this.token.actor ? Boolean(this.token.actor.system.size) : false;
return context;
}
}

View file

@ -675,16 +675,21 @@ export default class CharacterSheet extends DHBaseActorSheet {
roll: {
trait: button.dataset.attribute
},
hasRoll: true
};
const result = await this.document.diceRoll({
...config,
hasRoll: true,
actionType: 'action',
headerTitle: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`,
title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
ability: abilityLabel
})
});
};
const result = await this.document.diceRoll(config);
/* This could be avoided by baking config.costs into config.resourceUpdates. Didn't feel like messing with it at the time */
const costResources = result.costs
.filter(x => x.enabled)
.map(cost => ({ ...cost, value: -cost.value, total: -cost.total }));
config.resourceUpdates.addResources(costResources);
await config.resourceUpdates.updateResources();
}
//TODO: redo toggleEquipItem method

View file

@ -183,7 +183,6 @@ export default function DHApplicationMixin(Base) {
for (const deltaInput of htmlElement.querySelectorAll('input[data-allow-delta]')) {
deltaInput.dataset.numValue = deltaInput.value;
deltaInput.inputMode = 'numeric';
deltaInput.pattern = '^[+=\\-]?\d*';
const handleUpdate = (delta = 0) => {
const min = Number(deltaInput.min) || 0;

View file

@ -34,7 +34,10 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
}
}
],
dragDrop: [{ dragSelector: '.inventory-item[data-type="attack"]', dropSelector: null }]
dragDrop: [
{ dragSelector: '.inventory-item[data-type="attack"]', dropSelector: null },
{ dragSelector: ".currency[data-currency] .drag-handle", dropSelector: null }
]
};
/* -------------------------------------------- */
@ -254,14 +257,35 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
/* Application Drag/Drop */
/* -------------------------------------------- */
async _onDrop(event) {
event.stopPropagation();
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
if (data.type === 'Currency' && ['character', 'party'].includes(this.document.type)) {
const originActor = await foundry.utils.fromUuid(data.originActor);
if (!originActor || originActor.uuid === this.document.uuid) return;
const currency = data.currency;
const quantity = await game.system.api.applications.dialogs.ItemTransferDialog.configure({
originActor,
targetActor: this.document,
currency
});
if (quantity) {
originActor.update({ [`system.gold.${currency}`]: Math.max(0, originActor.system.gold[currency] - quantity) });
this.document.update({ [`system.gold.${currency}`]: this.document.system.gold[currency] + quantity });
}
return;
}
return super._onDrop(event);
}
async _onDropItem(event, item) {
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
const physicalActorTypes = ['character', 'party'];
const originActor = item.actor;
if (
item.actor?.uuid === this.document.uuid ||
!originActor ||
!physicalActorTypes.includes(this.document.type)
!['character', 'party'].includes(this.document.type)
) {
return super._onDropItem(event, item);
}
@ -270,10 +294,10 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
if (item.system.metadata.isInventoryItem) {
if (item.system.metadata.isQuantifiable) {
const actorItem = originActor.items.get(data.originId);
const quantityTransfered =
actorItem.system.quantity === 1
? 1
: await game.system.api.applications.dialogs.ItemTransferDialog.configure(item);
const quantityTransfered = await game.system.api.applications.dialogs.ItemTransferDialog.configure({
item,
targetActor: this.document
});
if (quantityTransfered) {
if (quantityTransfered === actorItem.system.quantity) {
@ -314,6 +338,16 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
* @param {DragEvent} event - The drag event
*/
async _onDragStart(event) {
// Handle drag/dropping currencies
const currencyEl = event.currentTarget.closest(".currency[data-currency]");
if (currencyEl) {
const currency = currencyEl.dataset.currency;
const data = { type: 'Currency', currency, originActor: this.document.uuid };
event.dataTransfer.setData('text/plain', JSON.stringify(data));
return;
}
// Handle drag/dropping attacks
const attackItem = event.currentTarget.closest('.inventory-item[data-type="attack"]');
if (attackItem) {
const attackData = {
@ -340,4 +374,4 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
super._onDragStart(event);
}
}
}

View file

@ -77,6 +77,7 @@ export default class BeastformSheet extends DHBaseItemSheet {
name: context.document.system.advantageOn[key].value
}))
);
context.dimensionsDisabled = context.document.system.tokenSize.size !== 'custom';
break;
case 'effects':
context.effects.actives = context.effects.actives.map(effect => {

View file

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

View file

@ -17,4 +17,30 @@ export default class DhActorDirectory extends foundry.applications.sidebar.tabs.
: null;
};
}
/** @inheritDoc */
_onDragStart(event) {
let actor;
const { entryId } = event.currentTarget.dataset;
if (entryId) {
actor = this.collection.get(entryId);
if (!actor?.visible) return false;
}
super._onDragStart(event);
// Create the drag preview.
if (actor && canvas.ready) {
const img = event.currentTarget.querySelector('img');
const pt = actor.prototypeToken;
const usesSize = actor.system.metadata.usesSize;
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
const width = usesSize ? tokenSizes[actor.system.size] : pt.width;
const height = usesSize ? tokenSizes[actor.system.size] : pt.height;
const w = width * canvas.dimensions.size * Math.abs(pt.texture.scaleX) * canvas.stage.scale.x;
const h = height * canvas.dimensions.size * Math.abs(pt.texture.scaleY) * canvas.stage.scale.y;
const preview = foundry.applications.ux.DragDrop.implementation.createDragImage(img, w, h);
event.dataTransfer.setDragImage(preview, w / 2, h / 2);
}
}
}

View file

@ -55,27 +55,28 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
];
}
addChatListeners = async (app, html, data) => {
addChatListeners = async (document, html, data) => {
const message = data?.message ?? document.toObject(false);
html.querySelectorAll('.simple-roll-button').forEach(element =>
element.addEventListener('click', event => this.onRollSimple(event, data.message))
element.addEventListener('click', event => this.onRollSimple(event, message))
);
html.querySelectorAll('.ability-use-button').forEach(element =>
element.addEventListener('click', event => this.abilityUseButton(event, data.message))
element.addEventListener('click', event => this.abilityUseButton(event, message))
);
html.querySelectorAll('.action-use-button').forEach(element =>
element.addEventListener('click', event => this.actionUseButton(event, data.message))
element.addEventListener('click', event => this.actionUseButton(event, message))
);
html.querySelectorAll('.reroll-button').forEach(element =>
element.addEventListener('click', event => this.rerollEvent(event, data.message))
element.addEventListener('click', event => this.rerollEvent(event, message))
);
html.querySelectorAll('.group-roll-button').forEach(element =>
element.addEventListener('click', event => this.groupRollButton(event, data.message))
element.addEventListener('click', event => this.groupRollButton(event, message))
);
html.querySelectorAll('.group-roll-reroll').forEach(element =>
element.addEventListener('click', event => this.groupRollReroll(event, data.message))
element.addEventListener('click', event => this.groupRollReroll(event, message))
);
html.querySelectorAll('.group-roll-success').forEach(element =>
element.addEventListener('click', event => this.groupRollSuccessEvent(event, data.message))
element.addEventListener('click', event => this.groupRollSuccessEvent(event, message))
);
html.querySelectorAll('.group-roll-header-expand-section').forEach(element =>
element.addEventListener('click', this.groupRollExpandSection)