Skip to content

Commit

Permalink
[foundryvtt#729] Handle dragging into actor sheet & delete items when…
Browse files Browse the repository at this point in the history
… container is delete
  • Loading branch information
arbron committed Nov 3, 2022
1 parent 770e8d5 commit a6ae9e4
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 38 deletions.
1 change: 1 addition & 0 deletions lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@
"DND5E.ComponentVerbal": "Verbal",
"DND5E.ComponentVerbalAbbr": "V",
"DND5E.Container": "Container",
"DND5E.ContainerRecursiveError": "Containers cannot be moved to contain themselves.",
"DND5E.Contents": "Contents",
"DND5E.ConBlinded": "Blinded",
"DND5E.ConCharmed": "Charmed",
Expand Down
44 changes: 28 additions & 16 deletions module/applications/actor/base-sheet.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -851,38 +851,50 @@ export default class ActorSheet5e extends ActorSheet {

/** @inheritdoc */
async _onDropItem(event, data) {
// If origin is not on actor & no contents, continue normally
// If origin is not on actor but has contents, create item & contents
// If original item has container, move out of container
return super._onDropItem(event, data);
if ( !this.actor.isOwner ) return false;
const item = await Item.implementation.fromDropData(data);

// Handle moving out of container & item sorting
if ( this.actor.uuid === item.parent?.uuid ) {
if ( item.system.container !== null ) await item.update({"system.container": null});
return this._onSortItem(event, item.toObject());
}

return this._onDropItemCreate(item);
}

/* -------------------------------------------- */

// Container Drag & Drop
// Drag from container to actor (handled by Actor sheet, only modify container contents)
// Drag from actor into container (handled on Item sheet, only modify container contents)
// Drag from elsewhere to actor (handled by Actor sheet, create new items)
// Drag from elsewhere to container (handled by Item sheet, create new items)

/** @override */
async _onDropItemCreate(itemData) {
let items = itemData instanceof Array ? itemData : [itemData];
const itemsWithoutAdvancement = items.filter(i => !i.system.advancement?.length);
/** @inheritdoc */
async _onDropItemCreate(item) {
let items = item instanceof Array ? item : [item];
const itemsWithoutAdvancement = items.filter(i => !i.system?.advancement?.length);
const multipleAdvancements = (items.length - itemsWithoutAdvancement.length) > 1;
if ( multipleAdvancements && !game.settings.get("dnd5e", "disableAdvancements") ) {
ui.notifications.warn(game.i18n.format("DND5E.WarnCantAddMultipleAdvancements"));
items = itemsWithoutAdvancement;
}

const toCreate = [];
const contentsToCreate = [];
for ( const item of items ) {
const result = await this._onDropSingleItem(item);
if ( result ) toCreate.push(result);
if ( result ) {
toCreate.push(result);
contentsToCreate.push(await item.system.contents);
}
}

// Create the owned items as normal
return this.actor.createEmbeddedDocuments("Item", toCreate);
const created = await this.actor.createEmbeddedDocuments("Item", toCreate);

// Add contents of any containers
const contentsData = Array.from(contentsToCreate.entries()).reduce((arr, [idx, items]) => [...arr,
...Array.from(items).map(i => foundry.utils.mergeObject(i.toObject(), {"system.container": created[idx].id}))
], []);
if ( contentsData.length ) this.actor.createEmbeddedDocuments("Item", contentsData);

return created;
}

/* -------------------------------------------- */
Expand Down
10 changes: 7 additions & 3 deletions module/applications/item/item-sheet.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -647,12 +647,16 @@ export default class ItemSheet5e extends ItemSheet {
const item = await Item.implementation.fromDropData(data);
if ( !this.item.isOwner || !item ) return false;

// TODO: Ensure only physical items are added to containers

// TODO: If item already exists in this container, just adjust its sorting
if ( item.system.container === this.item.id ) {
console.log("SORT ITEM");
return;
return false;
}

// Prevent dropping containers within themselves
const parentContainers = await this.item.system.allContainers();
if ( (this.item.uuid === item.uuid) || parentContainers.includes(item) ) {
return ui.notifications.error(game.i18n.localize("DND5E.ContainerRecursiveError"));
}

// If item already exists in same DocumentCollection, just adjust its container property
Expand Down
13 changes: 6 additions & 7 deletions module/data/item/backpack.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ export default class BackpackData extends SystemDataModel.mixin(ItemDescriptionT
get contentsWeight() {
if ( this.parent?.pack && !this.parent?.isEmbedded ) return this._contentsWeight();
return this.contents.reduce((weight, item) =>
weight + (item.type === "backpack")
? item.system.totalWeight : (item.system.weight * item.system.quantity)
weight + (item.type === "backpack" ? item.system.totalWeight
: (item.system.weight * item.system.quantity))
, this.currency.weight);
}

Expand All @@ -139,11 +139,10 @@ export default class BackpackData extends SystemDataModel.mixin(ItemDescriptionT
*/
async _contentsWeight() {
const contents = await this.contents;
return contents.reduce(async (weight, item) => {
const itemWeight = item.type === "backpack"
? await item.system.totalWeight : (item.system.weight * item.system.quantity);
return await weight + itemWeight;
}, this.currency.weight);
return contents.reduce(async (weight, item) =>
await weight + (item.type === "backpack" ? await item.system.totalWeight
: (item.system.weight * item.system.quantity))
, this.currency.weight);
}

/* -------------------------------------------- */
Expand Down
23 changes: 23 additions & 0 deletions module/data/item/templates/physical-item.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ export default class PhysicalItemTemplate extends foundry.abstract.DataModel {
};
}

/* -------------------------------------------- */
/* Migrations */
/* -------------------------------------------- */

/** @inheritdoc */
Expand Down Expand Up @@ -67,4 +69,25 @@ export default class PhysicalItemTemplate extends foundry.abstract.DataModel {
);
source.rarity = rarity;
}

/* -------------------------------------------- */
/* Helper Methods */
/* -------------------------------------------- */

/**
* All of the containers this item is within up to the parent actor or collection.
* @returns {Promise<Item5e[]>}
*/
async allContainers() {
let item = this.parent;
let container;
let depth = 0;
const containers = [];
while ( (container = await item.container) && (depth < 20) ) {
containers.push(container);
item = container;
depth++;
}
return containers;
}
}
23 changes: 11 additions & 12 deletions module/documents/item.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2109,14 +2109,8 @@ export default class Item5e extends Item {
*/
async _renderContainers(options, formerContainer) {
// Render this item's container & any containers it is within
let item = this;
let container;
let depth = 0;
while ( (container = await item.container) && (depth < 10) ) {
container.sheet?.render(false, options);
item = container;
depth++;
}
const parentContainers = await this.system.allContainers();
parentContainers.forEach(c => c.sheet?.render(false, options));

// Render the actor sheet, compendium, or sidebar
if ( this.isEmbedded ) this.actor.sheet?.render(false, options);
Expand Down Expand Up @@ -2224,13 +2218,18 @@ export default class Item5e extends Item {
/* -------------------------------------------- */

/** @inheritdoc */
_onDelete(options, userId) {
async _onDelete(options, userId) {
super._onDelete(options, userId);
// TODO: Deleting a container should also delete its contents
if ( (userId !== game.user.id) || !this.parent ) return this._renderContainers();
if ( userId !== game.user.id ) return this._renderContainers();

// Delete a container's contents when it is deleted
const contents = await this.system.contents;
if ( contents && !options.retainContents ) {
await Item.deleteDocuments(Array.from(contents.map(i => i.id)), {pack: this.pack, parent: this.parent});
}

// Assign a new original class
if ( (this.type === "class") && (this.id === this.parent.system.details.originalClass) ) {
if ( this.parent && (this.type === "class") && (this.id === this.parent.system.details.originalClass) ) {
this.parent._assignPrimaryClass();
}

Expand Down

0 comments on commit a6ae9e4

Please sign in to comment.