So I went back and dug into the code in Albion to remind myself how I did it, and lo and behold I’d ripped up my original version and done it (almost) how sproingie said - homogeneous stacking is done within each Item (using that interface above) and heterogeneous stacking is handled in the UI layer. (Actually it’s done in a ‘perception’ layer which filters raw inventory into something the UI can do, but that’s just so that all UI code can use the same filtering code).
The actual homogeneous stacking is done in the inventory:
/** Adds the item to the inventory
* Returns true if the item was merged/stacked with an existing item
*/
public boolean addItem(Item newItem)
{
// First check if we can hard stack with any existing items
boolean wasStacked = false;
for (Item existingItem : items)
{
if (existingItem.tryStack(newItem))
{
wasStacked = true;
break;
}
}
// Can't hard stack, so we'll just add the item directly
if (!wasStacked)
items.add(newItem);
return wasStacked;
}
Removing an item is pretty easy too:
public Item removeCount(Item existingItem, final int count)
{
assert (items.contains(existingItem));
if (existingItem.getCount() > count)
{
Item unstacked = existingItem.split(1);
return unstacked;
}
else if (existingItem.getCount() == count)
{
removeItem(existingItem);
return existingItem;
}
else
{
// Oh noes! Tried to split a stack but stack isn't big enough
assert (false);
return null;
}
}
This is (conceptually) just a way of making an inventory of 1000 gold coins actually represented by one Coin object with an internal ‘count’ of 1000, rather than 1000 individual Coin objects. tryStack() is implemented for each item type, and (usually) just checks if the two objects are the same object type, then increments an internal counter.
The perception filter for the UI just runs through all items in a list and produces a Description of each item based on the current character’s stats. A Description is basically image+name+description. Items which produce identical Descriptions are ‘soft’ stacked together ( Map<Description, ArrayList>) for the ui to use. Any ui command which operates on one of these lists (eg. equip) randomly operates on one of the items within it.