Design Docs - Thinking About Inventory - Part 3

All this design, pseudo-code, diagrams, and no code! How do I know all of this actually works in practice? To be truthful, I don't! That's the fun of sitting in the clouds and playing the role of architecture-God, right? 😆

I'm just pulling everyone's leg. Let's actually start writing some code to actually test out what we've put together so far in Part 1, and Part 2. The aim for this part of the discussion is to implement an inventory system in which characters can use, equip, and unequip items.

Get the Code

For the lazy, if you just want to see code immediately, here is a link to all the code files in the GitHub respository: jrpg-inventory-system.

https://github.com/urbanspr1nter/jrpg-inventory-system

Project Setup

I've recently been mostly a JavaScript-first guy, but for this project, I'll be working with C#. Most of the pseudo-code (if you can call it that) have been similar to this syntax since I had intended to be coding in Visual Studio from the very beginning.

The project will be in dot net core 2.1, paired with xUnit for testing. I used Visual Studio 2019 Community for this, so all of this can be done for free.

The intention is to create a simple C# project and implement everything that has been presented so far. I will also write some test code in a separate test project that will check to see if the ideas that have been put in place work well in practice.

This project will be the project which will be built out over time in the next few posts, and will be refactored as needed.

My hope is that by the end of this series of discussions, we'll have an inventory system that works for a JRPG.

Implementation

Alright, so here's how far we've come so far.

 

Our inventory system in UML so far

Our inventory in UML so far.

 

Let's tackle each class one-by-one. Create a project called InventorySystem. We'll write our POCO-type classes first. We see that the following fit that criteria: ItemData, and ItemInfo.

ItemData.cs
namespace InventorySystem
{
    public class ItemData
    {
        public int Quantity { get; set; }
        public IItem Item { get; set; }
    }
}

ItemInfo.cs
namespace InventorySystem
{
    public class ItemInfo
    {
        public ItemName Name { get; set; }
        public int Quantity { get; set; }
        public string Description { get; set; }
    }
}

Pretty simple so far. Now, we need to define IItem, and ItemName. I think it makes sense to do ItemName first. It's actually just a simple enum for now.

ItemName.cs
using System.ComponentModel;
namespace InventorySystem
{
    public enum ItemName
    {
        [Description("Potion")]
        Potion,
        [Description("Leather Helmet")]
        LeatherHelmet,
        [Description("Iron Helmet")]
        IronHelmet
    }
}

We've start out very simple. Let's just do 3 types of items, 1 consumeable and 2 equipment: Potion, LeatherHelmet, and IronHelmet. We'll just need to make sur we update this enum as we add more items down the line. Take note that this is something which can probably refactored to be more dynamic. Of course, we'll take a look in the future. 😄 .

IItem.cs
namespace InventorySystem
{
    public interface IItem
    {
        bool CanApply(Character targetChar);
        bool Apply(Character targetChar, BodyPart targetBodyPart);
        bool UndoApply(Character targetChar);
    }
}

IItem is exactly like the way we have specified it. This will be the base class in which Potion, Leather Helmet and Iron Helmet will implement.

Okay, so far, so good! Everything is nice and straightfoward so far. Now, it's time to look at the other side of our program. Looks like we also have another enum called BodyPart. Here's the implementation:

BodyPart.cs
using System.ComponentModel;
namespace InventorySystem
{
    public enum BodyPart
    {
        [Description("Default")]
        Default,

        [Description("Head")]
        Head,

        [Description("Chest")]
        Chest,

        [Description("Legs")]
        Legs,

        [Description("Arms")]
        Arms,

        [Description("Hands")]
        Hands,

        [Description("Accessory")]
        Accessory
    }
}

Just like ItemName, the concept is straightforward and self-explanatory. I do want to remind everyone though that, consumeables should always target the Default body part. Our system relies on this proper usage in order to "use" our items correctly.

To model a body using a bunch of BodyParts, we'll need to create the CharacterBody class to reflect a composition of BodyPart and ItemName association. When interpreted, this is basically a "mini-registry" of items which a character currently has on their body -- status effects applied after the fact.

CharacterBody.cs
using System.Collections.Generic;
namespace InventorySystem
{
    public class CharacterBody
    {
        private Dictionary<BodyPart, ItemName?> body;

        public CharacterBody()
        {
            body = new Dictionary<BodyPart, ItemName?>();
        }

        public ItemName? Get(BodyPart bodyPart)
        {
            if(!body.ContainsKey(bodyPart))
            {
                body.Add(bodyPart, null);
            }

            return body[bodyPart];
        }

        public void Set(BodyPart bodyPart, ItemName itemName)
        {
            body[bodyPart] = itemName;
        }

        public ItemName? Remove(BodyPart bodyPart)
        {
            var result = body[bodyPart];

            body[bodyPart] = null;

            return result;
        }
    }
}

One important thing to note here is that Get and Remove return Nullable types. I've purposely done this to treat null as a currently empty slot for a specific BodyPart. I think this will be easier to show in the UI as the CharacterBody type is now guaranteed to have all BodyParts available for use. It's just that some will be "empty", through representation of a null value.

Remove also now returns an ItemName. I think it makes sense to actually know what item was removed from a specific body part. It allows the item to "easily" be placed back into the registry that way.

We also have one more POCO-style class to implement. I didn't draw it out in the previous UML diagrams simply because I had forgotten. All JRPG character need statzzz right? Let's model out the HP, MP, Level, Experience, Strength, etc. stats into a simple POCO object and have the Character hold an instance of that.

All fields will be publicly visible to make life easier to augment an attribute, for now.

CharacterStatistics.cs
namespace InventorySystem
{
    public class CharacterStatistics
    {
        public int HP { get; set; }
        public int MP { get; set; }
        public int LVL { get; set; }
        public int EXP { get; set; }
        public int ATK { get; set; }
        public int MAG { get; set; }
        public int DEF { get; set; }
        public int SPR { get; set; }
    }
}

Finally, let's dive into the last 2 "big" classes. InventoryManager and Character.

InventoryManager

Yeah, having fat "manager" classes is cheap, but for our small inventory system, it actually works quite well. The interesting thing which I've started to notice about making a JRPG is that the game mechanics are quite simple, and the implementation of game-play patterns aren't too difficult.

I'm starting to think that it is actually extending a JRPG through features, and fixing bugs is where most of the difficulty arises when creating a JRPG game. Creating maintainable code seems to be challenging, and I'm pretty convinced that a JRPG is essentially one big exercise in object-oriented programming.

Because of that, I've been quite cognizant in treading this fine line in not over-engineering our inventory system. The last thing I want is to create something which is difficult to use and understand, but at the same time, get something done so quickly that it becomes difficult to maintain in the future.

It's all deceptively difficult, and probably the main reason why I haven't ever dellivered a game yet, even after so many attempts.

I wonder if I would be able to finish one if I just stop trying to create RPGs and started out with something more simple, like a basic 2D shooter?

 

InventoryManager.cs
using System.Collections.Generic;
namespace InventorySystem
{
    public class InventoryManager
    {
        private Dictionary<ItemName, ItemData> registry;

        public InventoryManager(Dictionary<ItemName, ItemData> initialRegistry)
        {
            registry = initialRegistry;
        }

        public List<ItemInfo> QueryAll()
        {
            List<ItemInfo> data = new List<ItemInfo>();

            foreach(var name in registry.Keys)
            {
                data.Add(new ItemInfo {
                    Quantity = registry[name].Quantity,
                    Name = name
                });
            }

            return data;
        }

        public bool Use(ItemName name, Character srcChar,
            Character targetChar, BodyPart targetBodyPart)
        {
            if(registry[name].Quantity <= 0)
            {
                return false;
            }

            IItem item = registry[name].Item;

            if(!item.CanApply(targetChar))
            {
                return false;
            }

            item.Apply(targetChar, targetBodyPart);

            if(targetBodyPart != BodyPart.Default)
            {
                targetChar.EquipItem(name, targetBodyPart);
            }

            registry[name].Quantity--;

            return true;
        }

        public bool Drop(ItemName name)
        {
            return false;
        }

        public bool Restore(ItemName name, Character targetChar)
        {
            IItem item = registry[name].Item;

            registry[name].Quantity++;

            item.UndoApply(targetChar);

            return true;
        }

        public bool Acquire(ItemName name)
        {
            return false;
        }

        public bool HasItem(ItemName name)
        {
            return registry.ContainsKey(name) &&
                registry[name].Quantity > 0;
        }
    }
}


You'll notice immediately that InventoryManager takes in an initial registry through dependency injection. I've done this for now to make things testable. I want to inject an initial data set of items without exposing the registry itself to all the clients.

Other things:

  1. I've bought into the decision that having factory instances to generate IItem instances in ItemData as a silly idea. For now, let's treat IItem as "immutable" in that sense. In the Use, and Restore methods I've decided to reference the IItem instance directly.
  2. If Character will potentially call the InventoryManager instance, and InventoryManager's Use method actually calls the Character method EquipItem, does that make it... a circular reference? Yes, that's a smell, but my justification is that the method call is a different Character instance. Okay, not a convincing argument, but let's see what problems will come up -- if at all.

All this code should be familiar. The only thing that's left unimplemented for now are the Drop and Acquire methods which we will implement in later discussions.

Character

The purpose of this project is to create an inventory system for a JRPG, and not a character system. We'll do that some other time. However, we can't get away with not having some sort of basic Character data type since the inventory actions between items and characters are coupled rather tightly.

Because of that nature, we're not left with much choice but to implement a simple Character class for now. This Character class just has the basic attributes in which our items may need to augment.

Character.cs
namespace InventorySystem
{
    public class Character
    {
        private CharacterBody body;
        public CharacterStatistics Statistics { get; }
        public string Name { get; }

        private InventoryManager inventoryManager;

        public Character(string name, InventoryManager gameInventoryManager)
        {
            body = new CharacterBody();
            Name = name;
            Statistics = new CharacterStatistics();
            inventoryManager = gameInventoryManager;
        }

        public bool UseItem(ItemName name, Character targetChar, BodyPart targetBodyPart)
        {
            return inventoryManager.Use(name, this, targetChar, targetBodyPart);
        }

        public bool EquipItem(ItemName name, BodyPart targetBodyPart)
        {
            body.Set(targetBodyPart, name);
            return true;
        }

        public bool UnequipItem(BodyPart bodyPart)
        {
            ItemName? itemName = body.Get(bodyPart);

            if(itemName != null)
            {
                inventoryManager.Restore((ItemName)itemName, this);
                body.Remove(bodyPart);
                return true;
            }
            return false;
        }
    }
}

  • We've injected an instance of InventoryManager to a Character instance here. It's so that we can use this reference around in all of its methods.
  • CharacterBody should not be exposed to any clients. Instead, the methods of EquipItem and UnequipItem should indirectly alter the body's state.
  • CharacterStatistics is exposed publicly on purpose. It's to make it easier for IItems to alter the target character's attributes when effects are applied.

Creating Items

Alright! Now we're getting closer to the fun part. We're one step closer to creating testable code, but in order to that we need some items!

We need to implement the 3 items in which we had specificed in our ItemName enum.

  1. Potion
  2. Leather Helmet
  3. Iron Helmet

 

Fun fact, and probably pretty hilarious... Whenever I think of Potion, I have this visual in my head that associates it with the Potion found in the early Gen 1 Pokemon TCG:

Pokemon TCG Potion Trainer

Pokemon TCG - Potion Trainer

I was 9 years old at the time, and at that point had played JRPGs for the NES and SNES, but really, no JRPG at that time had a memorable "Potion" graphic as the JRPGs represented items through text and cues at that time. In my opinion, the representation of a Potion in the Pokemon TCG is probably the most awesome depiction of a Potion for this very nostalgic reason.

Potion.cs
using System;
namespace InventorySystem.Items
{
    public class Potion : IItem
    {
        public bool Apply(Character targetChar, BodyPart targetBodyPart)
        {
            targetChar.Statistics.HP += 10;

            return true;
        }

        public bool CanApply(Character targetChar)
        {
            if(targetChar.Statistics.HP >= 100)
            {
                return false;
            }

            return true;
        }

        public bool UndoApply(Character targetChar)
        {
            throw new NotImplementedException();
        }
    }
}

For the sake of brevity, we'll just say that a Potion will heal 10 HP at a time for the target character and won't be usable if the character has 100 HP, or more. Since Potions are consumables, their effects cannot be "undone".

LeatherHelmet.cs
namespace InventorySystem.Items
{
    public class LeatherHelmet : IItem
    {
        public bool Apply(Character targetChar, BodyPart targetBodyPart)
        {
            targetChar.Statistics.DEF += 3;
            return true;
        }

        public bool CanApply(Character targetChar)
        {
            return true;
        }

        public bool UndoApply(Character targetChar)
        {
            targetChar.Statistics.DEF -= 3;
            return true;
        }
    }
}

A basic starter helmet for most RPGs. This leather helmet, when equipped, gives a +3 defense stat boost to the target character. When unequipped, the stat boost disappears. All types of characters should be able to equip the leather helmet.

IronHelmet.cs
namespace InventorySystem.Items
{
    public class IronHelmet : IItem
    {
        public bool Apply(Character targetChar, BodyPart targetBodyPart)
        {
            targetChar.Statistics.DEF += 7;
            return true;
        }

        public bool CanApply(Character targetChar)
        {
            return true;
        }

        public bool UndoApply(Character targetChar)
        {
            targetChar.Statistics.DEF -= 7;
            return true;
        }
    }
}

A stronger version of leather helmet, iron helmet will give a +7 defense stat boost to the target character. Same as leather helmet, unequipping will remove the stat boost, and all types of characters should be able to equip the iron helmet.

Unit Tests

Great! We have finally implemented everything we need to use, equip and unequip items. Let's actually write some tests to see this code working!

 

The Visual Studio Solution space so far

Our Visual Studio Solution so far.

 

Create a new project called InventorySystemTests within the same solution. It will be an xUnit test project.

Since I don't work for Microsoft anymore, I don't have to feel guilty about writing plugs. My opinion has always been that Visual Studio is a superior product, and what's cool about Visual Studio Community is that you can be a productive developer through the whole lifecycle of development, and testing included.

I'm making a big deal out of this because I remember the Community editions of VS actually lacked unit testing years ago. Either that or Test Explorer jus wasn't that great in those versions, and I had always found myself shelving out cash for the Professional editions. Or, maybe I'm making it all up.

We'll write the following unit tests:

  1. A character should be able to heal themselves with a Potion. The inventory should also reflect the change in quantity in the number of Potions available.
  2. A character should be able to heal another character with a Potion. The inventory should also reflect the change in quantity in the number of Potions available.
  3. A character should be able to heal another character with a Potion and should also be able to heal themselves. The inventory should also reflect the change in quantity in the number of Potions available.
  4. A character should be able to equip and unequip our Leather and Iron helmets. The statistics of all characters equpping and unequipping items should be reflective of the current helmet being worn.

 

Let's write some initialization code to set up some of the objects we'll be using frequently across test methods. Our unit test class will hold a reference to InventoryManager which will be initialized in every test case.

private InventoryManager inventoryManager;

We'll also have 2 methods -- GetInitialRegistry which loads up all items along with quantity to be used in every test method, and QueryFor which will return back the ItemInfo for a particular ItemName.

private Dictionary<ItemName, ItemData> GetInitialRegistry()
{
  Dictionary<ItemName, ItemData> initialRegistry =
  	new Dictionary<ItemName, ItemData> {
    { ItemName.Potion, new ItemData { Quantity = 3, Item = new Potion() } },
    { ItemName.LeatherHelmet, new ItemData { Quantity = 2, Item = new LeatherHelmet() } },
    { ItemName.IronHelmet, new ItemData { Quantity = 1, Item = new IronHelmet() } }
  };

	return initialRegistry;
}

private ItemInfo QueryFor(ItemName name)
{
  List<ItemInfo> data = inventoryManager.QueryAll();
  ItemInfo info = data.Find(x => x.Name == name);

  return info;
}

In the test session, we'll then have 3 Potions, 2 Leather Helmet and 1 Iron Helmet available for each test case being run.

Let's tackle our first test case.

UnitTest::TestPotionOnSelf
[Fact]
public void TestPotionOnSelf()
{
  Dictionary<ItemName, ItemData> initialRegistry = GetInitialRegistry();

  inventoryManager =
  	new InventoryManager(initialRegistry);

  Character terra = new Character("Terra", inventoryManager);
  Assert.Equal(0, terra.Statistics.HP);

  bool useResult;

  useResult = terra.UseItem(ItemName.Potion, terra, BodyPart.Default);
  Assert.True(useResult);
  Assert.Equal(10, terra.Statistics.HP);
  Assert.Equal(2, QueryFor(ItemName.Potion).Quantity);

  useResult = terra.UseItem(ItemName.Potion, terra, BodyPart.Default);
  Assert.True(useResult);
  Assert.Equal(20, terra.Statistics.HP);
  Assert.Equal(1, QueryFor(ItemName.Potion).Quantity);

  useResult =  terra.UseItem(ItemName.Potion, terra, BodyPart.Default);
  Assert.True(useResult);
  Assert.Equal(30, terra.Statistics.HP);
  Assert.Equal(0, QueryFor(ItemName.Potion).Quantity);

  useResult = terra.UseItem(ItemName.Potion, terra, BodyPart.Default);
  Assert.False(useResult);
  Assert.Equal(30, terra.Statistics.HP);
  Assert.Equal(0, QueryFor(ItemName.Potion).Quantity);
}
  1. Initalize the registry and use that to create an InventoryManager instance.
  2. Create our character, Terra with an initial HP of 0.
  3. Continuously use the Potions available and assert for the values.
  4. Make sure to see that using an item with 0 quantity has no effect on the character!!!

 

UnitTest::TestPotionOnTarget
[Fact]
public void TestPotionOnTarget()
{
  Dictionary<ItemName, ItemData> initialRegistry = GetInitialRegistry();

  inventoryManager =
  	new InventoryManager(initialRegistry);

  Character terra = new Character("Terra", inventoryManager);
  Character locke = new Character("Locke", inventoryManager);

  Assert.Equal(0, terra.Statistics.HP);
  Assert.Equal(0, locke.Statistics.HP);

  bool useResult;

  useResult = terra.UseItem(ItemName.Potion, locke, BodyPart.Default);
  Assert.True(useResult);
  Assert.Equal(10, locke.Statistics.HP);
  Assert.Equal(2, QueryFor(ItemName.Potion).Quantity);

  useResult = terra.UseItem(ItemName.Potion, locke, BodyPart.Default);
  Assert.True(useResult);
  Assert.Equal(20, locke.Statistics.HP);
  Assert.Equal(1, QueryFor(ItemName.Potion).Quantity);

  useResult = terra.UseItem(ItemName.Potion, locke, BodyPart.Default);
  Assert.True(useResult);
  Assert.Equal(30, locke.Statistics.HP);
  Assert.Equal(0, QueryFor(ItemName.Potion).Quantity);

  useResult = terra.UseItem(ItemName.Potion, locke, BodyPart.Default);
  Assert.False(useResult);
  Assert.Equal(30, locke.Statistics.HP);
  Assert.Equal(0, QueryFor(ItemName.Potion).Quantity);
}

Pretty much exactly the same test as the first, except that now Terra will apply potions onto Locke.

UnitTest::TestPotionOnSelfAndTarget
[Fact]
public void TestPotionOnSelfAndTarget()
{
  Dictionary<ItemName, ItemData> initialRegistry = GetInitialRegistry();

  inventoryManager =
  	new InventoryManager(initialRegistry);

  Character terra = new Character("Terra", inventoryManager);
  Character locke = new Character("Locke", inventoryManager);

  Assert.Equal(0, terra.Statistics.HP);
  Assert.Equal(0, locke.Statistics.HP);

  bool useResult;

  useResult = terra.UseItem(ItemName.Potion, locke, BodyPart.Default);
  Assert.True(useResult);
  Assert.Equal(10, locke.Statistics.HP);
  Assert.Equal(0, terra.Statistics.HP);
  Assert.Equal(2, QueryFor(ItemName.Potion).Quantity);

  useResult = locke.UseItem(ItemName.Potion, terra, BodyPart.Default);
  Assert.True(useResult);
  Assert.Equal(10, terra.Statistics.HP);
  Assert.Equal(10, locke.Statistics.HP);
  Assert.Equal(1, QueryFor(ItemName.Potion).Quantity);

  useResult = terra.UseItem(ItemName.Potion, terra, BodyPart.Default);
  Assert.True(useResult);
  Assert.Equal(20, terra.Statistics.HP);
  Assert.Equal(10, locke.Statistics.HP);
  Assert.Equal(0, QueryFor(ItemName.Potion).Quantity);

  useResult = locke.UseItem(ItemName.Potion, terra, BodyPart.Default);
  Assert.False(useResult);
  Assert.Equal(20, terra.Statistics.HP);
  Assert.Equal(10, locke.Statistics.HP);
  Assert.Equal(0, QueryFor(ItemName.Potion).Quantity);
}

Just to prove we can apply Items on separate characters, the Potions are applied on both Terra and Locke at various moments. Check for the HP to make sure the Potions have been properly applied.

UnitTest::TestEquip
[Fact]
public void TestEquip()
{
  Dictionary<ItemName, ItemData> initialRegistry = GetInitialRegistry();

  inventoryManager =
  	new InventoryManager(initialRegistry);

  Character terra = new Character("Terra", inventoryManager);
  Character locke = new Character("Locke", inventoryManager);

  Assert.Equal(0, terra.Statistics.DEF);
  Assert.Equal(0, locke.Statistics.DEF);

  bool useResult;

  useResult = terra.UseItem(ItemName.LeatherHelmet, terra, BodyPart.Head);
  Assert.True(useResult);
  Assert.Equal(0, locke.Statistics.DEF);
  Assert.Equal(3, terra.Statistics.DEF);
  Assert.Equal(1, QueryFor(ItemName.LeatherHelmet).Quantity);

  useResult = locke.UseItem(ItemName.LeatherHelmet, locke, BodyPart.Head);
  Assert.True(useResult);
  Assert.Equal(3, locke.Statistics.DEF);
  Assert.Equal(3, terra.Statistics.DEF);
  Assert.Equal(0, QueryFor(ItemName.LeatherHelmet).Quantity);

  useResult = terra.UnequipItem(BodyPart.Head);
  Assert.True(useResult);
  Assert.Equal(3, locke.Statistics.DEF);
  Assert.Equal(0, terra.Statistics.DEF);
  Assert.Equal(1, QueryFor(ItemName.LeatherHelmet).Quantity);

  useResult = locke.UseItem(ItemName.IronHelmet, terra, BodyPart.Head);
  Assert.True(useResult);
  Assert.Equal(3, locke.Statistics.DEF);
  Assert.Equal(7, terra.Statistics.DEF);
  Assert.Equal(1, QueryFor(ItemName.LeatherHelmet).Quantity);
  Assert.Equal(0, QueryFor(ItemName.IronHelmet).Quantity);

  useResult = locke.UnequipItem(BodyPart.Head);
  Assert.True(useResult);
  Assert.Equal(0, locke.Statistics.DEF);
  Assert.Equal(7, terra.Statistics.DEF);
  Assert.Equal(2, QueryFor(ItemName.LeatherHelmet).Quantity);
  Assert.Equal(0, QueryFor(ItemName.IronHelmet).Quantity);

  useResult = terra.UnequipItem(BodyPart.Head);
  Assert.True(useResult);
  Assert.Equal(0, locke.Statistics.DEF);
  Assert.Equal(0, terra.Statistics.DEF);
  Assert.Equal(2, QueryFor(ItemName.LeatherHelmet).Quantity);
  Assert.Equal(1, QueryFor(ItemName.IronHelmet).Quantity);
}

Ah! A very interesting test. Here, we get to see EquipItem and UnequipItem in action.

The Terra and Locke characters are created, and we first equip Terra with a leather helmet. Immediately, we see her DEF stat goes up to 3. After equipping the helmet onto her, we now only have 1 Leather Helmet.

The second Leather Helmet goes to Locke, and we see his DEF stat goes up to 3. At this point, there should be no more Leather Helmets in inventory.

Suppose now, we want to equip Terra with our Iron Helmet. We only have 1, so let's give that to her. We first unequip the current helmet. This was a Leather Helmet, and now we find that the quantity is set properly in our inventory.

Equipping the Iron Helmet then gives Terra a 7 defense stat. Knowing that we only had 1 Iron Helmet to start with, and is now with Terra, the expectation for the number of Iron Helmets within our inventory should be 0.

All good there! Okay, how about unequipping both Terra and Locke to restore our inventory? After unequipping both the Leather Helmet on Locke, and Iron Helmet on Terra, we see both defense stat of the characters drop back down to 0. The item quantities within our inventory are properly restored.

Let's run the tests!

 

Test run result

Test run result

 

Sweet! It all passes! 👏👏👏 We've done a fine job so far!

If you want to see all the code, this is the place to go: https://github.com/urbanspr1nter/jrpg-inventory-system

Summary

Alright, so we've accomplished our main goal so far for our inventory system. The system designed so far has been able to do the following:

  • Use consumable items
  • Equip items
  • Unequip items

 

We'll spend some of the next session implementing acquisition of items, and dropping the items. Perhaps maybe there will be some time to attach Value to items so that we can somehow start on purchasing/selling items in a town. Hmmm... 🤔

 

Well, until next time!