Inventory

The inventory features are designed for use with Entity types. For an Entity to have an inventory it should implement IHasInventory.

Basic Concepts

  • Any object that can be associated with an entity's inventory is considered "loot". All loot inherits LootBase.
  • All loot has the following properties
    • UnitCount: How many units of that item are represented by the instance. This allows for the concept of 'stacking': a Pencil instance with a UnitCount of 12 would represent a bundle of 12 pencils.
      • Must be a positive whole number greater than zero
      • Cannot exceed the value of UnitsPerStack
    • UnitsPerStack: The maximum number of units an instance may represent. For example if a Pencil has a UnitsPerStack value of 12 and you want to give the implementor 13 units, you'd need one instance of Pencil with a UnitCount of 12, and another with a UnitCount of 1... or, one instance with a UnitCount of 6 and another with a UnitCount of 7, etc. To make an item 'unstackable' set its UnitsPerStack value to 1.
      • Must be a positive whole number greater than zero.
    • UnitWeight: How much each unit weighs, in a measurement system that is left to you to implement. Another property, TotalWeight returns the product of UnitWeight and UnitCount.
      • Must be a positive whole number or zero.
  • Loot is stored in a Bag, and some loot can be equipped to the owner (if it inherits EquipmentBase, which inherits LootBase). A bag itself is equipment. In order for a bag to store anything, it must be equipped.
  • Equipped items are mapped to EquipmentSlot instances, which each represent a separate location on your implementor where you can place that type of equipment. In the FigmentShare singleton instance you define what slots exist in your game, and then in your implementing types, which of those slots they will use.
  • Your interaction with an implementor's loot and equipment will be through the implementor's Inventory property.
Note: Technically there's no reason we couldn't dispense with UnitCount and create a unique instance for every unit; we could handle 'stacking' at the presentation layer based on the UnitsPerStack value. It's just more efficient to combine 2 (or 20, or 2000) identical instances into a single instance via the UnitCount property. If you do wish to implement a 1-unit-per-instance design then you can always set both UnitCount and UnitsPerStack to 1.


IHasInventory requires the property Inventory, which returns an InventoryCollection instance. InventoryCollection provides the Equipment property, which is a ReadOnlyDictionary<EquipmentSlot,EquipmentBase> collection, and several methods for managing its items.

The basic interaction is: myImplementor.Inventory[EquipmentSlot].

Equipment

An EquipmentSlot acts as a key to an EquipmentBase value in the implementor's InventoryCollection. The slot provides a Type ContentType property that represents what types (of EquipmentBase subclasses) it will accept.

So, you might have an EquipmentSlot object that accepts only Helmet types, and this slot would represent the location on your implementor's body where a helmet goes, the skull. This provides the means to control what equipment your implementors wear, and how they wear it. You can define multiple slots that accept the same content type - say, Ring1 and Ring2; but the slots must have unique Name properties, for example: "Ring One" and "Ring Two", or "Left Hand Ring" and "Right Hand Ring".

Creating slots

In some management class in your game (the Game1 class in XNA, for example) you would instantiate some EquipmentSlot instances by calling your FigmentShare instance's CreateEquipmentSlot<T> method. Any IHasInventory implementor in your game will refer to one or more of these instances in their Inventory. You define each slot once; thereafter your implementors reference that instance.

// Assuming a variable 'share' which references a FigmentShare instance, 
// and a subclass of EquipmentBase 'ChestArmor' which represents chest armor:

share.CreateEquipmentSlot<ChestArmor>("Chest Armor"); 

There now exists in the the share.EquipmentSlots collection a slot with the Name "Chest Armor", which accepts objects that derive from ChestArmor. Add as many as you need to cover the equipment requirements of all your implementing types.

You can then facilitate referencing slots by adding properties to your game management class.

public class Game1
{
    ...
    public EquipmentSlot ChestArmorSlot { get { return share.GetEquipmentSlotByName("Chest Armor"); } }
    public EquipmentSlot LegArmorSlot { get { return share.GetEquipmentSlotByName("Leg Armor"); } }
    public EquipmentSlot WeaponSlot { get { return share.GetEquipmentSlotByName("Weapon"); } }
   ...
} 

Referencing Slots in Your Implementor

You might create a Player class which uses chest and leg armor slots, and a weapon slot. A ScaryMonster class might use only the weapon slot.

public class Player : Entity, IHasInventory
{
    private readonly InventoryCollection _inventory;

    public Player(Game1 game)
    {
        Inventory.AddEquipmentSlot(game.ChestArmorSlot);
        Inventory.AddEquipmentSlot(game.LegArmorSlot);
        Inventory.AddEquipmentSlot(game.WeaponSlot);
    }
} 

public class ScaryMonster : Entity, IHasInventory
{
    private readonly InventoryCollection _inventory;

    public ScaryMonster(Game1 game)
    {
        Inventory.AddEquipmentSlot(game.WeaponSlot);
    }
} 

Bag

The base loot storage type for IHasInventory implementors is Bag<T>, where T is the subclass of LootBase which the bag accepts derivatives of. Bag<T> has a Collection<T> which represents its contents; the Bag class itself provides proxy methods to interact with the contents collection - it behaves as if you are dealing with its Contents collection directly.

Bag<T> inherits EquipmentBase; it is equipment that holds loot (items derived from LootBase). A bag must be equipped in order to have contents, and it cannot be added to the contents of another Bag<> unless its own contents are empty - you cannot store non-empty bags in other bags. Bags have a preset UnitsPerStack value of 1 - they cannot be 'stacked'.

Bags also have the properties MaxItems and MaxWeight. These values are nullable. MaxItems restricts the bag to a maximum number of items it can hold at one time. MaxWeight restricts the maximum total weight of the items it contains. You can use either or both of these properties. You can also set them to null, which "turns off" limiting by the respective method.

Since Bag<T> is built around a generic collection (where T : LootBase), you can create bags that either accept any form of loot, or are specialized for, say, Herbs, or SpellScrolls, or Arrows (like a quiver).

Note that if loot cannot be equipped then the only way to store it is in a Bag<>, and that bag must be equipped to carry anything. So the minimum slot loadout for anyone you want to be able to carry loot that is not equipped is one slot that accepts a Bag<T> where T: LootBase.

Bag Operations Examples

Get all equipped bags:
 myImplementor.Inventory.Bags; 

Get all equipped bags which accept only ammo:
 myImplementor.Inventory.GetBags<Ammo>(); 

Add 20 arrows to an equipped quiver:
var arrows = CreateArrows(20); // Some method you wrote to generate arrows
var quiverSlot = myImplementor.Inventory[game.QuiverSlot];
if (quiverSlot!=null) { quiverSlot.Add(arrows); } 

Quicker Slot Indexing

Much of the interaction with the inventory will be done by the player via the user interface. Once you've set up representations of the slots in the GUI, it's just a matter of handling the clicks on the objects tied to the slot indices.

You may have concluded that an easier means of indexing slots is to create specific properties in your implementing class that refer to the properties you set up in your game manager class above. Once you decide that, for example, a Player class has one slot representing a backpack and another representing a quiver, you can address them like so.

public class Player : Entity, IHasInventory
{
    ...
    public Bag<LootBase> Pack { get { return Inventory[game.PackSlot]; } }
    public Bag<Arrow> Quiver { get { return Inventory[game.QuiverSlot]; } }
    ...
} 

if (player.Pack != null) player.Pack.Add(something); 

Other InventoryCollection members

bool EquipItem(out EquipmentBase foundItem, EquipmentSlot slot, Equipment item)
Sets the value of the specified slot key in Equipment to the specified item. Returns true if successful. This method handles checking the item type against the ContentType of the slot and will throw an exception if an incorrect type is passed. If an item was already assigned to the slot, it unequips that item and sends it to the 'out' parameter.

EquipmentBase UnequipItem(EquipmentSlot slot)
Returns the item assigned to the specified key in the Equipment collection, or null.

Equipping an item in a slot and adding the previous content of that slot (if any) to a bag with free space:
EquipmentBase foundHelmet;
EquipmentBase newHelmet = CreateHelmet(); // Some method you wrote to generate a helmet
if (Inventory.Equip(out foundItem, HelmetSlot, newHelmet))
{
    if (foundItem == null) return;

    var bagWithSpace = myIHasInventory.Inventory.FindBagWithSpace<LootBase>();
    if (bagWithSpace == null) 
    { 
        // Do something with the previously equipped item 
        return;
    }

    bagWithSpace.Add(foundHelmet);
} 

Last edited Aug 25, 2011 at 9:45 PM by Furiant, version 39