Skip to content

Commit

Permalink
Merge pull request #4 from foundryvtt/containers-data
Browse files Browse the repository at this point in the history
[#729] Add container to PhysicalItemTemplate, improve how item weight is calculated
  • Loading branch information
arbron authored Nov 21, 2023
2 parents 0e8395a + 595a94d commit c6f915d
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 11 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"eqeqeq": ["warn", "smart"],
"func-call-spacing": "warn",
"func-names": ["warn", "never"],
"getter-return": "warn",
"getter-return": ["warn", { "allowImplicit": true }],
"lines-between-class-members": "warn",
"new-parens": ["warn", "always"],
"no-alert": "warn",
Expand Down
1 change: 1 addition & 0 deletions lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,7 @@
"DND5E.ComponentSomaticAbbr": "S",
"DND5E.ComponentVerbal": "Verbal",
"DND5E.ComponentVerbalAbbr": "V",
"DND5E.Container": "Container",
"DND5E.Contents": "Contents",
"DND5E.ContextMenuActionEdit": "Edit",
"DND5E.ContextMenuActionDuplicate": "Duplicate",
Expand Down
2 changes: 1 addition & 1 deletion module/applications/actor/character-sheet.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
// Organize items
for ( let i of items ) {
const ctx = context.itemContext[i.id] ??= {};
ctx.totalWeight = (i.system.quantity * i.system.weight).toNearest(0.1);
ctx.totalWeight = i.system.totalWeight?.toNearest(0.1);
inventory[i.type].items.push(i);
}

Expand Down
4 changes: 2 additions & 2 deletions module/applications/actor/vehicle-sheet.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
// Handle cargo explicitly
const isCargo = item.flags.dnd5e?.vehicleCargo === true;
if ( isCargo ) {
totalWeight += (item.system.weight || 0) * item.system.quantity;
totalWeight += item.system.totalWeight ?? 0;
cargo.cargo.items.push(item);
continue;
}
Expand All @@ -244,7 +244,7 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
else features.actions.items.push(item);
break;
default:
totalWeight += (item.system.weight || 0) * item.system.quantity;
totalWeight += item.system.totalWeight ?? 0;
cargo.cargo.items.push(item);
}
}
Expand Down
82 changes: 82 additions & 0 deletions module/data/item/container.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export default class ContainerData extends SystemDataModel.mixin(
/** @inheritdoc */
static defineSchema() {
return this.mergeSchema(super.defineSchema(), {
quantity: new foundry.data.fields.NumberField({min: 1, max: 1}),
capacity: new foundry.data.fields.SchemaField({
type: new foundry.data.fields.StringField({
required: true, initial: "weight", blank: false, label: "DND5E.ItemContainerCapacityType"
Expand All @@ -34,4 +35,85 @@ export default class ContainerData extends SystemDataModel.mixin(
}, {label: "DND5E.ItemContainerCapacity"})
});
}

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

/** @inheritdoc */
static _migrateData(source) {
super._migrateData(source);
ContainerData.#migrateQuantity(source);
}

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

/**
* Force quantity to always be 1.
* @param {object} source The candidate source data from which the model will be constructed.
*/
static #migrateQuantity(source) {
source.quantity = 1;
}

/* -------------------------------------------- */
/* Getters */
/* -------------------------------------------- */

/**
* Get all of the items contained in this container. A promise if item is within a compendium.
* @type {Collection<Item5e>|Promise<Collection<Item5e>>}
*/
get contents() {
if ( !this.parent ) return new foundry.utils.Collection();

// If in a compendium, fetch using getDocuments and return a promise
if ( this.parent.pack && !this.parent.isEmbedded ) {
const pack = game.packs.get(this.parent.pack);
return pack.getDocuments({system: { container: this.parent.id }}).then(d =>
new foundry.utils.Collection(d.map(d => [d.id, d]))
);
}

// Otherwise use local document collection
return (this.parent.isEmbedded ? this.parent.actor.items : game.items).reduce((collection, item) => {
if ( item.system.container === this.parent.id ) collection.set(item.id, item);
return collection;
}, new foundry.utils.Collection());
}

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

/**
* Weight of the items in this container. Result is a promise if item is within a compendium.
* @type {number|Promise<number>}
*/
get contentsWeight() {
if ( this.parent?.pack && !this.parent?.isEmbedded ) return this.#contentsWeight();
return this.contents.reduce((weight, item) => weight + item.system.totalWeight, this.currencyWeight);
}

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

/**
* Asynchronous helper method for calculating the weight of items in a compendium.
* @returns {Promise<number>}
*/
async #contentsWeight() {
const contents = await this.contents;
return contents.reduce(async (weight, item) => await weight + await item.system.totalWeight, this.currencyWeight);
}

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

/**
* The weight of this container with all of its contents. Result is a promise if item is within a compendium.
* @type {number|Promise<number>}
*/
get totalWeight() {
if ( this.capacity.weightless ) return this.weight;
const containedWeight = this.contentsWeight;
if ( containedWeight instanceof Promise ) return containedWeight.then(c => this.weight + c);
return this.weight + containedWeight;
}
}
14 changes: 14 additions & 0 deletions module/data/item/templates/physical-item.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import SystemDataModel from "../../abstract.mjs";
/**
* Data model template with information on physical items.
*
* @property {string} container Container within which this item is located.
* @property {number} quantity Number of items in a stack.
* @property {number} weight Item's weight in pounds or kilograms (depending on system setting).
* @property {object} price
Expand All @@ -16,6 +17,9 @@ export default class PhysicalItemTemplate extends SystemDataModel {
/** @inheritdoc */
static defineSchema() {
return {
container: new foundry.data.fields.ForeignDocumentField(foundry.documents.BaseItem, {
idOnly: true, label: "DND5E.Container"
}),
quantity: new foundry.data.fields.NumberField({
required: true, nullable: false, integer: true, initial: 1, min: 0, label: "DND5E.Quantity"
}),
Expand Down Expand Up @@ -49,6 +53,16 @@ export default class PhysicalItemTemplate extends SystemDataModel {
return hasPrice ? `${value} ${CONFIG.DND5E.currencies[denomination].label}` : null;
}

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

/**
* The weight of all of the items in an item stack.
* @type {number}
*/
get totalWeight() {
return this.quantity * this.weight;
}

/* -------------------------------------------- */
/* Migrations */
/* -------------------------------------------- */
Expand Down
17 changes: 17 additions & 0 deletions module/data/shared/currency.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,21 @@ export default class CurrencyTemplate extends SystemDataModel {
}), {initialKeys: CONFIG.DND5E.currencies, initialKeysOnly: true, label: "DND5E.Currency"})
};
}

/* -------------------------------------------- */
/* Getters */
/* -------------------------------------------- */

/**
* Get the weight of all of the currency. Always returns 0 if currency weight is disabled in settings.
* @returns {number}
*/
get currencyWeight() {
if ( !game.settings.get("dnd5e", "currencyWeight") ) return 0;
const count = Object.values(this.currency).reduce((count, value) => count + value, 0);
const currencyPerWeight = game.settings.get("dnd5e", "metricWeightUnits")
? CONFIG.DND5E.encumbrance.currencyPerWeight.metric
: CONFIG.DND5E.encumbrance.currencyPerWeight.imperial;
return count / currencyPerWeight;
}
}
10 changes: 3 additions & 7 deletions module/documents/actor/actor.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -543,13 +543,9 @@ export default class Actor5e extends SystemDocumentMixin(Actor) {
const encumbrance = this.system.attributes.encumbrance ??= {};

// Get the total weight from items
const physicalItems = ["weapon", "equipment", "consumable", "tool", "backpack", "loot"];
let weight = this.items.reduce((weight, i) => {
if ( !physicalItems.includes(i.type) ) return weight;
const q = i.system.quantity || 0;
const w = i.system.weight || 0;
return weight + (q * w);
}, 0);
let weight = this.items
.filter(item => !item.container)
.reduce((weight, item) => weight + (item.system.totalWeight ?? 0), 0);

// [Optional] add Currency Weight (for non-transformed actors)
const currency = this.system.currency;
Expand Down
16 changes: 16 additions & 0 deletions module/documents/item.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export default class Item5e extends SystemDocumentMixin(Item) {
return this.system.isActive ?? false;
}

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

/**
* Which ability score modifier is used by this item?
* @type {string|null}
Expand All @@ -41,6 +43,20 @@ export default class Item5e extends SystemDocumentMixin(Item) {

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

/**
* The item that contains this item, if it is in a container. Returns a promise if the item is located
* in a compendium pack.
* @type {Item5e|Promise<Item5e>|void}
*/
get container() {
if ( !this.system.container ) return;
if ( this.isEmbedded ) return this.actor.items.get(this.system.container);
if ( this.pack ) return game.packs.get(this.pack).getDocument(this.system.container);
return game.items.get(this.system.container);
}

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

/**
* What is the critical hit threshold for this item, if applicable?
* @type {number|null}
Expand Down

0 comments on commit c6f915d

Please sign in to comment.