Make all item types quantifiable in the party actor

This commit is contained in:
Carlos Fernandez 2026-04-16 02:02:26 -04:00
parent 76a3268cf3
commit 9aab9fca52
13 changed files with 91 additions and 45 deletions

View file

@ -38,13 +38,15 @@ export default class ItemTransferDialog extends HandlebarsApplicationMixin(Appli
originActor ??= item?.actor; originActor ??= item?.actor;
const homebrewKey = CONFIG.DH.SETTINGS.gameSettings.Homebrew; const homebrewKey = CONFIG.DH.SETTINGS.gameSettings.Homebrew;
const currencySetting = game.settings.get(CONFIG.DH.id, homebrewKey).currency?.[currency] ?? null; const currencySetting = game.settings.get(CONFIG.DH.id, homebrewKey).currency?.[currency] ?? null;
const max = item?.system.quantity ?? originActor.system.gold[currency] ?? 0;
return { return {
originActor, originActor,
targetActor, targetActor,
itemImage: item?.img, itemImage: item?.img,
currencyIcon: currencySetting?.icon, currencyIcon: currencySetting?.icon,
max: item?.system.quantity ?? originActor.system.gold[currency] ?? 0, max,
initial: targetActor.system.metadata.quantifiable?.includes(item.type) ? max : 1,
title: item?.name ?? currencySetting?.label title: item?.name ?? currencySetting?.label
}; };
} }

View file

@ -298,47 +298,79 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
); );
} }
if (item.system.metadata.isQuantifiable) { // Perform the actual transfer, showing a dialog when doing it
const actorItem = originActor.items.get(data.originId); const availableQuantity = Math.max(1, item.system.quantity);
const quantityTransfered = await game.system.api.applications.dialogs.ItemTransferDialog.configure({ const actorItem = originActor.items.get(data.originId) ?? item;
if (availableQuantity > 1) {
const quantityTransferred = await game.system.api.applications.dialogs.ItemTransferDialog.configure({
item, item,
targetActor: this.document targetActor: this.document
}); });
return this.#transferItem(actorItem, quantityTransferred);
if (quantityTransfered) {
const existingItem = this.document.items.find(x => itemIsIdentical(x, item));
if (existingItem) {
await existingItem.update({
'system.quantity': existingItem.system.quantity + quantityTransfered
});
} else {
const createData = item.toObject();
await this.document.createEmbeddedDocuments('Item', [
{
...createData,
system: {
...createData.system,
quantity: quantityTransfered
}
}
]);
}
if (quantityTransfered === actorItem.system.quantity) {
await originActor.deleteEmbeddedDocuments('Item', [data.originId]);
} else {
await actorItem.update({
'system.quantity': actorItem.system.quantity - quantityTransfered
});
}
}
} else { } else {
await this.document.createEmbeddedDocuments('Item', [item.toObject()]); return this.#transferItem(actorItem, availableQuantity);
await originActor.deleteEmbeddedDocuments('Item', [data.originId]);
} }
} }
} }
/**
* Helper to perform the actual transfer of an item to this actor, including stack/unstack logic based on target quantifiability.
* Make sure item is the actor item before calling this method or there will be issues
*/
async #transferItem(item, quantity) {
const originActor = item.actor;
const targetActor = this.document;
const allowStacking = targetActor.system.metadata.quantifiable?.includes(item.type);
const batch = [];
// First add/update the item to the target actor
const existing = allowStacking ? targetActor.items.find(x => itemIsIdentical(x, item)) : null;
if (existing) {
batch.push({
action: 'update',
documentName: 'Item',
parent: targetActor,
updates: [{ '_id': existing.id, 'system.quantity': existing.system.quantity + quantity }]
});
} else {
const itemsToCreate = [];
if (allowStacking) {
itemsToCreate.push(foundry.utils.mergeObject(item.toObject(true), { system: { quantity } }));
} else {
const createData = new Array(Math.max(1, quantity))
.fill(0)
.map(() => foundry.utils.mergeObject(item.toObject(), { system: { quantity: 1 } }));
itemsToCreate.push(...createData);
}
batch.push({
action: 'create',
documentName: 'Item',
parent: targetActor,
data: itemsToCreate
});
}
// Remove the item from the original actor (by either deleting it, or updating its quantity)
if (quantity >= item.system.quantity) {
batch.push({
action: 'delete',
documentName: 'Item',
parent: originActor,
ids: [item.id]
});
} else {
batch.push({
action: 'update',
documentName: 'Item',
parent: originActor,
updates: [{ '_id': item.id, 'system.quantity': item.system.quantity - quantity }]
});
}
return foundry.documents.modifyBatch(batch);
}
/** /**
* On dragStart on the item. * On dragStart on the item.
* @param {DragEvent} event - The drag event * @param {DragEvent} event - The drag event

View file

@ -19,7 +19,8 @@ export default class DhCharacter extends DhCreature {
type: 'character', type: 'character',
settingSheet: DHCharacterSettings, settingSheet: DHCharacterSettings,
isNPC: false, isNPC: false,
hasInventory: true hasInventory: true,
quantifiable: ["loot", "consumable"]
}); });
} }

View file

@ -8,7 +8,8 @@ export default class DhParty extends BaseDataActor {
/** @inheritdoc */ /** @inheritdoc */
static get metadata() { static get metadata() {
return foundry.utils.mergeObject(super.metadata, { return foundry.utils.mergeObject(super.metadata, {
hasInventory: true hasInventory: true,
quantifiable: ["weapon", "armor", "loot", "consumable"]
}); });
} }

View file

@ -23,6 +23,7 @@ export default class DHArmor extends AttachableItem {
current: new fields.NumberField({ integer: true, min: 0, initial: 0 }), current: new fields.NumberField({ integer: true, min: 0, initial: 0 }),
max: new fields.NumberField({ required: true, integer: true, initial: 0 }) max: new fields.NumberField({ required: true, integer: true, initial: 0 })
}), }),
quantity: new fields.NumberField({ integer: true, initial: 1, min: 0, required: true }),
baseThresholds: new fields.SchemaField({ baseThresholds: new fields.SchemaField({
major: new fields.NumberField({ integer: true, initial: 0 }), major: new fields.NumberField({ integer: true, initial: 0 }),
severe: new fields.NumberField({ integer: true, initial: 0 }) severe: new fields.NumberField({ integer: true, initial: 0 })

View file

@ -7,7 +7,6 @@ export default class DHConsumable extends BaseDataItem {
label: 'TYPES.Item.consumable', label: 'TYPES.Item.consumable',
type: 'consumable', type: 'consumable',
hasDescription: true, hasDescription: true,
isQuantifiable: true,
isInventoryItem: true, isInventoryItem: true,
hasActions: true hasActions: true
}); });
@ -18,7 +17,8 @@ export default class DHConsumable extends BaseDataItem {
const fields = foundry.data.fields; const fields = foundry.data.fields;
return { return {
...super.defineSchema(), ...super.defineSchema(),
consumeOnUse: new fields.BooleanField({ initial: true }) consumeOnUse: new fields.BooleanField({ initial: true }),
quantity: new fields.NumberField({ integer: true, initial: 1, min: 0, required: true })
}; };
} }

View file

@ -7,7 +7,6 @@ export default class DHLoot extends BaseDataItem {
label: 'TYPES.Item.loot', label: 'TYPES.Item.loot',
type: 'loot', type: 'loot',
hasDescription: true, hasDescription: true,
isQuantifiable: true,
isInventoryItem: true, isInventoryItem: true,
hasActions: true hasActions: true
}); });
@ -15,8 +14,10 @@ export default class DHLoot extends BaseDataItem {
/** @inheritDoc */ /** @inheritDoc */
static defineSchema() { static defineSchema() {
const fields = foundry.data.fields;
return { return {
...super.defineSchema() ...super.defineSchema(),
quantity: new fields.NumberField({ integer: true, initial: 1, min: 0, required: true }),
}; };
} }

View file

@ -26,6 +26,7 @@ export default class DHWeapon extends AttachableItem {
label: 'DAGGERHEART.GENERAL.Tiers.singular' label: 'DAGGERHEART.GENERAL.Tiers.singular'
}), }),
equipped: new fields.BooleanField({ initial: false }), equipped: new fields.BooleanField({ initial: false }),
quantity: new fields.NumberField({ integer: true, initial: 1, min: 0, required: true }),
//SETTINGS //SETTINGS
secondary: new fields.BooleanField({ initial: false, label: 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon' }), secondary: new fields.BooleanField({ initial: false, label: 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon' }),

View file

@ -14,7 +14,7 @@
<div class="form-group"> <div class="form-group">
<label>{{localize "DAGGERHEART.GENERAL.quantity"}}</label> <label>{{localize "DAGGERHEART.GENERAL.quantity"}}</label>
<div class="form-fields"> <div class="form-fields">
<range-picker step="1" min="1" max="{{max}}" name="quantity" value="{{max}}"></range-picker> <range-picker step="1" min="1" max="{{max}}" name="quantity" value="{{initial}}"></range-picker>
</div> </div>
</div> </div>
</div> </div>

View file

@ -39,6 +39,7 @@
collection=@root.inventory.consumables collection=@root.inventory.consumables
isGlassy=true isGlassy=true
canCreate=true canCreate=true
isQuantifiable=true
}} }}
{{> 'daggerheart.inventory-items' {{> 'daggerheart.inventory-items'
title='TYPES.Item.loot' title='TYPES.Item.loot'
@ -47,6 +48,7 @@
isGlassy=true isGlassy=true
canCreate=true canCreate=true
showActions=true showActions=true
isQuantifiable=true
}} }}
</div> </div>
</section> </section>

View file

@ -29,6 +29,7 @@
canCreate=true canCreate=true
hideResources=true hideResources=true
hideContextMenu=true hideContextMenu=true
isQuantifiable=true
}} }}
{{> 'daggerheart.inventory-items' {{> 'daggerheart.inventory-items'
title='TYPES.Item.armor' title='TYPES.Item.armor'
@ -39,6 +40,7 @@
canCreate=true canCreate=true
hideResources=true hideResources=true
hideContextMenu=true hideContextMenu=true
isQuantifiable=true
}} }}
{{> 'daggerheart.inventory-items' {{> 'daggerheart.inventory-items'
title='TYPES.Item.consumable' title='TYPES.Item.consumable'
@ -48,6 +50,7 @@
isGlassy=true isGlassy=true
canCreate=true canCreate=true
hideContextMenu=true hideContextMenu=true
isQuantifiable=true
}} }}
{{> 'daggerheart.inventory-items' {{> 'daggerheart.inventory-items'
title='TYPES.Item.loot' title='TYPES.Item.loot'
@ -57,6 +60,7 @@
isGlassy=true isGlassy=true
canCreate=true canCreate=true
hideContextMenu=true hideContextMenu=true
isQuantifiable=true
}} }}
</div> </div>
</section> </section>

View file

@ -66,6 +66,7 @@ Parameters:
showLabels=../showLabels showLabels=../showLabels
isAction=../isAction isAction=../isAction
hideResources=../hideResources hideResources=../hideResources
isQuantifiable=../isQuantifiable
showActions=../showActions showActions=../showActions
}} }}

View file

@ -63,10 +63,10 @@ Parameters:
{{#if (and (not hideResources) (not (eq item.system.resource.type 'diceValue')))}} {{#if (and (not hideResources) (not (eq item.system.resource.type 'diceValue')))}}
{{> "systems/daggerheart/templates/sheets/global/partials/item-resource.hbs"}} {{> "systems/daggerheart/templates/sheets/global/partials/item-resource.hbs"}}
{{/if}} {{/if}}
{{#if (and (not hideResources) (gte item.system.quantity 0))}} {{#if (or isQuantifiable (or (eq item.system.quantity 0) (gt item.system.quantity 1)))}}
<div class="item-resource"> <div class="item-resource">
<input type="number" id="{{item.uuid}}-quantity" class="inventory-item-quantity" value="{{item.system.quantity}}" min="0" /> <input type="number" id="{{item.uuid}}-quantity" class="inventory-item-quantity" value="{{item.system.quantity}}" min="0" />
</div> </div>
{{/if}} {{/if}}
{{!-- Controls --}} {{!-- Controls --}}