Game Development Community

RPG Inventory Control System

by Ed Johnson · in Torque Game Engine · 04/07/2005 (7:44 pm) · 14 replies

In a RPG programming book they had a section on making a map and character inventory tracker. All the inventory resources are really old and dont seem to be relevent anymore. I looked at Spock's C++ inventory to see how i'd implement a c++ inventory to torque's inner workings (script) its a bit complicated though. Since I bet some people out there are still tryin to get a good inventory system up, ill post this and see where it goes. it needs alot of work to get squeezed into torque, but i bet it'd be appreciated in the community.

If you'd like to see a working program using this, email me, it wouldnt be a problem.

Theres 2 parts, the MapICS handles object instances scattered around a map (with an X,Y,Z).. Each map would have a MapICS file list of items... (torque already has items that can be placed around, this is needed more:)
The CharICS handles personal inventory, with owners, containers, and sorting(using linked list)...
They both use special structures that track the MIL item numbers and maintains a linked list; and they both rely on a Master Item List or database of all game items, with each item having a unique identifier (number) that both the ICS's refer to. The MIL has single, unique instances while the ICS have many instances of any object.

Items belong to a map, a character, or a different item (a backpack). This means they need ownership, and an owner can have multiple instances of an object (i.e. coins). An owner's collection of items is an inventory list, and any number of instances of objects can be in it.

#1
04/07/2005 (7:48 pm)
MapICS.cc \\ map inventory control system, not really needed, torque does this well.
#include <windows.h>
#include <stdio.h>
#include "MapICS.h" 

cMapICS::cMapICS()
{
  m_NumItems = 0;
  m_ItemParent = NULL;
}

cMapICS::~cMapICS()
{
  Free();
}

BOOL cMapICS::Load(char *Filename) // loads inv list
{
  FILE *fp;
  long LongNum;
  sMapItem *Item, *ItemPtr = NULL;

  Free();  // Free a prior set

  // Open the file
  if((fp=fopen(Filename, "rb"))==NULL)
    return FALSE;

  // Loop forever reading in items
  while(1) {
    // Get next item number (break if no more items,
    // which is represented by a return value of -1).
    if((LongNum = GetNextLong(fp)) == -1)
      break;

    // Create a new map pointer and link it in
    Item = new sMapItem();
    if(ItemPtr == NULL)
      m_ItemParent = Item;
    else {
      Item->Prev = ItemPtr;
      ItemPtr->Next = Item;
    }
    ItemPtr = Item;

    // Store MIL item number
    Item->ItemNum = LongNum;

    // Get quantity
    Item->Quantity = GetNextLong(fp);

    // Get coordinates
    Item->XPos = GetNextFloat(fp);
    Item->YPos = GetNextFloat(fp);
    Item->ZPos = GetNextFloat(fp);

    // Get owner #
    Item->Owner = GetNextLong(fp);

    // Save index # and increase count
    Item->Index = m_NumItems++;
  }

  // Close the file
  fclose(fp);

  // Match objects that belong to others
  ItemPtr = m_ItemParent;
  while(ItemPtr != NULL) {

    // Check if this item belongs to another
    if(ItemPtr->Owner != -1) {

      // Find matching parent item
      Item = m_ItemParent;
      while(Item != NULL) {

        if(ItemPtr->Owner == Item->Index) {
          // A match, point to parent
          ItemPtr->Parent = Item;
          break; // Stop scanning for parents
        }

        Item = Item->Next;
      }
    }

    // Go to next item
    ItemPtr = ItemPtr->Next;
  }

  return TRUE;
}

BOOL cMapICS::Save(char *Filename)
{
  FILE *fp;
  sMapItem *Item;
  long Index = 0;

  // Open the file
  if((fp=fopen(Filename, "wb"))==NULL)
    return FALSE;

  // Assign index numbers to items
  if((Item = m_ItemParent) == NULL) {
    fclose(fp);
    return TRUE; // no items to save
  }
  while(Item != NULL) {
    Item->Index = Index++;
    Item = Item->Next;
  }

  // Match child items to parents
  Item = m_ItemParent;
  while(Item != NULL) {
    if(Item->Parent != NULL)
      Item->Owner = Item->Parent->Index;
    else
      Item->Owner = -1;
    Item = Item->Next;
  }

  // Save 'em out
  Item = m_ItemParent;
  while(Item != NULL) {
    // Item number
    fprintf(fp, "%lu\r\n", Item->ItemNum);

    // Quantity
    fprintf(fp, "%lu\r\n", Item->Quantity);

    // Coordinates
    fprintf(fp, "%lf\r\n%lf\r\n%lf\r\n", Item->XPos, Item->YPos, Item->ZPos);
     
    // Owner #
    fprintf(fp, "%ld\r\n", Item->Owner);

    // Next item
    Item = Item->Next;
  }

  fclose(fp); // Close the file

  return TRUE; // Return success!
}

BOOL cMapICS::Free()
{
  m_NumItems = 0;
  delete m_ItemParent;
  m_ItemParent = NULL;
  return TRUE;
}
#2
04/07/2005 (7:48 pm)
MapICS.cc - continued \\ map inventory control system, not really needed, torque does this well.
BOOL cMapICS::Add(long ItemNum, long Quantity,                \
                  float XPos, float YPos, float ZPos,         \
                  sMapItem *OwnerItem)
{
  sMapItem *Item;

  // Create a new item structure
  Item = new sMapItem();

  // Insert into top of list
  Item->Next = m_ItemParent;
  if(m_ItemParent != NULL)
    m_ItemParent->Prev = Item;
  m_ItemParent = Item;

  // Fill the item structure
  Item->ItemNum  = ItemNum;
  Item->Quantity = Quantity;
  Item->XPos     = XPos;
  Item->YPos     = YPos;
  Item->ZPos     = ZPos;
  Item->Parent   = OwnerItem;
  
  return TRUE;
}

BOOL cMapICS::Remove(sMapItem *Item)
{
  sMapItem *ItemPtr, *NextItem;
  
  // Remove child objects first
  if((ItemPtr = m_ItemParent) != NULL) {
    while(ItemPtr != NULL) {
      NextItem = ItemPtr->Next;
      if(ItemPtr->Parent == Item)
        Remove(ItemPtr);
      ItemPtr = NextItem;
    }
  }

  // Remove from linked list and reset root
  // if it's the current head of list.
  if(Item->Prev != NULL)
    Item->Prev->Next = Item->Next;
  else
    m_ItemParent = Item->Next;
  if(Item->Next != NULL)
    Item->Next->Prev = Item->Prev;

  // Clear link list
  Item->Prev = Item->Next = NULL;

  // Free memory
  delete Item;

  return TRUE;
}

long cMapICS::GetNumItems()
{
  return m_NumItems;
}

sMapItem *cMapICS::GetParentItem()
{
  return m_ItemParent;
}

sMapItem *cMapICS::GetItem(long Num)
{
  sMapItem *Item;

  Item = m_ItemParent;
  while(Num--) {
    if(Item == NULL)
      return NULL;
    Item = Item->Next;
  }
  return Item;
}

long cMapICS::GetNextLong(FILE *fp)
{
  char Buf[1024];
  long Pos = 0;
  int c;

  // Read until EOF or EOL
  while(1) {
    if((c = fgetc(fp)) == EOF)
      break;
    if(c == 0x0a)
      break;
    if((c >= '0' && c <= '9') || c == '.' || c == '-')
      Buf[Pos++] = c;
  }
  if(!Pos)
    return -1;
  Buf[Pos] = 0;

  return atol(Buf);
}

float cMapICS::GetNextFloat(FILE *fp)
{
  char Buf[1024];
  long Pos = 0;
  int c;

  // Read until EOF or EOL
  while(1) {
    if((c = fgetc(fp)) == EOF)
      break;
    if(c == 0x0a)
      break;
    if((c >= '0' && c <= '9') || c == '.' || c == '-')
      Buf[Pos++] = c;
  }
  Buf[Pos] = 0;

  return (float)atof(Buf);
}
#3
04/07/2005 (7:49 pm)
MapICS.h \\ map inventory control system, not really needed, torque does this well.
#ifndef _MAPICS_H_
#define _MAPICS_H_

typedef struct sMapItem
{
  long      ItemNum;          // Master-Item-List item number
  long      Quantity;         // Quantity of item (ie coins)
  float     XPos, YPos, ZPos; // Map coordinates

  sMapItem  *Prev, *Next;     // linked list pointers

  long       Index;           // This items index #
  long       Owner;           // Owner index #
  sMapItem  *Parent;          // Parent of a contained item

  sMapItem()  
  { 
    Prev = Next = Parent = NULL;
    Index = 0; Owner = -1;
   }
   
  ~sMapItem() { delete Next; } 
} sMapItem;

class cMapICS
{
  private:
    long      m_NumItems;    // # items in map
    sMapItem *m_ItemParent;  // Linked list parent map item
    
    // Functions to read in next long or float # in file
    long  GetNextLong(FILE *fp);
    float GetNextFloat(FILE *fp);

  public:
    cMapICS();   // Constructor
    ~cMapICS();  // Destructor

    // Load, save, and free a list of map items
    BOOL Load(char *Filename);
    BOOL Save(char *Filename);
    BOOL Free();

    // Add and remove an item on map
    BOOL Add(long ItemNum, long Quantity,                     \
             float XPos, float YPos, float ZPos,              \
             sMapItem *OwnerItem = NULL);
    BOOL Remove(sMapItem *Item);

    // Retrieve # items or parent linked list object
    long      GetNumItems();
    sMapItem *GetParentItem();
    sMapItem *GetItem(long Num);
};

#endif
#4
04/07/2005 (7:50 pm)
CharICS.cc character inventory control system, currently loads from files, could be setup for external or local database..
#include <windows.h>
#include <stdio.h>
#include "CharICS.h"

cCharICS::cCharICS()
{
  m_NumItems = 0;
  m_ItemParent = NULL;
}

cCharICS::~cCharICS()
{
  Free();
}

BOOL cCharICS::Load(char *Filename)
{
  FILE *fp;
  long LongNum;
  sCharItem *Item, *ItemPtr = NULL;

  Free();  // Free a prior set

  // Open the file
  if((fp=fopen(Filename, "rb"))==NULL)
    return FALSE;

  // Loop forever reading in items
  while(1) {
    // Get next item number (break if no more items,
    // which is represented by a return value of -1).
    if((LongNum = GetNextLong(fp)) == -1)
      break;

    // Create a new item pointer and link it in
    Item = new sCharItem();
    if(ItemPtr == NULL)
      m_ItemParent = Item;
    else {
      Item->Prev = ItemPtr;
      ItemPtr->Next = Item;
    }
    ItemPtr = Item;

    // Store MIL item number
    Item->ItemNum = LongNum;

    // Get quantity
    Item->Quantity = GetNextLong(fp);

    // Get owner #
    Item->Owner = GetNextLong(fp);

    // Save index # and increase count
    Item->Index = m_NumItems++;
  }

  // Close the file
  fclose(fp);

  // Match objects that belong to others
  ItemPtr = m_ItemParent;
  while(ItemPtr != NULL) {

    // Check if this item belongs to another
    if(ItemPtr->Owner != -1) {

      // Find matching parent item
      Item = m_ItemParent;
      while(Item != NULL) {

        if(ItemPtr->Owner == Item->Index) {
          // A match, point to parent
          ItemPtr->Parent = Item;
          break; // Stop scanning for parents
        }

        Item = Item->Next;
      }
    }

    // Go to next item
    ItemPtr = ItemPtr->Next;
  }

  return TRUE;
}

BOOL cCharICS::Save(char *Filename)
{
  FILE *fp;
  sCharItem *Item;
  long Index = 0;

  // Open the file
  if((fp=fopen(Filename, "wb"))==NULL)
    return FALSE;

  // Assign index numbers to items
  if((Item = m_ItemParent) == NULL) {
    fclose(fp);
    return TRUE; // no items to save
  }
  while(Item != NULL) {
    Item->Index = Index++;
    Item = Item->Next;
  }

  // Match child items to parents
  Item = m_ItemParent;
  while(Item != NULL) {
    if(Item->Parent != NULL)
      Item->Owner = Item->Parent->Index;
    else
      Item->Owner = -1;
    Item = Item->Next;
  }

  // Save 'em out
  Item = m_ItemParent;
  while(Item != NULL) {
    // Item number
    fprintf(fp, "%lu\r\n", Item->ItemNum);

    // Quantity
    fprintf(fp, "%lu\r\n", Item->Quantity);

    // Owner #
    fprintf(fp, "%ld\r\n", Item->Owner);

    // Next item
    Item = Item->Next;
  }

  fclose(fp); // Close the file

  return TRUE; // Return success!
}
#5
04/07/2005 (7:52 pm)
CharICS.cc - continued // character inventory control system
BOOL cCharICS::Free()
{
  m_NumItems = 0;
  delete m_ItemParent;
  m_ItemParent = NULL;
  return TRUE;
}

BOOL cCharICS::Add(long ItemNum, long Quantity,                \
                  sCharItem *OwnerItem)
{
  sCharItem *Item;

  // Create a new item structure
  Item = new sCharItem();

  // Insert into top of list
  Item->Next = m_ItemParent;
  if(m_ItemParent != NULL)
    m_ItemParent->Prev = Item;
  m_ItemParent = Item;

  // Fill the item structure
  Item->ItemNum  = ItemNum;
  Item->Quantity = Quantity;
  Item->Parent   = OwnerItem;
  
  return TRUE;
}

BOOL cCharICS::Remove(sCharItem *Item)
{
  sCharItem *ItemPtr, *NextItem;
  
  // Remove child objects first
  if((ItemPtr = m_ItemParent) != NULL) {
    while(ItemPtr != NULL) {
      NextItem = ItemPtr->Next;
      if(ItemPtr->Parent == Item)
        Remove(ItemPtr);
      ItemPtr = NextItem;
    }
  }

  // Remove from linked list and reset root
  // if it's the current head of list.
  if(Item->Prev != NULL)
    Item->Prev->Next = Item->Next;
  else
    m_ItemParent = Item->Next;
  if(Item->Next != NULL)
    Item->Next->Prev = Item->Prev;

  // Clear link list
  Item->Prev = Item->Next = NULL;

  // Free memory
  delete Item;

  return TRUE;
}

long cCharICS::GetNumItems()
{
  return m_NumItems;
}

sCharItem *cCharICS::GetParentItem()
{
  return m_ItemParent;
}

sCharItem *cCharICS::GetItem(long Num)
{
  sCharItem *Item;

  Item = m_ItemParent;
  while(Num--) {
    if(Item == NULL)
      return NULL;
    Item = Item->Next;
  }
  return Item;
}

BOOL cCharICS::Arrange()
{
  sCharItem *Item, *PrevItem;
  
  // Start at top of linked list and float
  // each item up that has a lesser ItemNum.
  // Break if past bottom of list
  Item = m_ItemParent;
  while(Item != NULL) {

    // Check previous item to float up
    if(Item->Prev != NULL) {

      // Keep floating up while prev item has
      // a lesser ItemNum value or until top
      // of list has been reached.
      while(Item->Prev != NULL) {
        PrevItem = Item->Prev;  // Get prev item pointer

        // Break if no more to float up
        if(Item->ItemNum >= PrevItem->ItemNum)
          break;

        // Swap 'em
        if((PrevItem = Item->Prev) != NULL) {
          if(PrevItem->Prev != NULL)
            PrevItem->Prev->Next = Item;

          if((PrevItem->Next = Item->Next) != NULL)
            Item->Next->Prev = PrevItem;

          if((Item->Prev = PrevItem->Prev) == NULL)
            m_ItemParent = Item;

          PrevItem->Prev = Item;
          Item->Next = PrevItem;
        }
      }
    }

    // Go to next object
    Item = Item->Next;
  }

  return TRUE;
}

BOOL cCharICS::MoveUp(sCharItem *Item)
{
  sCharItem *PrevItem;

  // Swap item and item before it
  if((PrevItem = Item->Prev) != NULL) {
    if(PrevItem->Prev != NULL)
      PrevItem->Prev->Next = Item;

    if((PrevItem->Next = Item->Next) != NULL)
      Item->Next->Prev = PrevItem;

    if((Item->Prev = PrevItem->Prev) == NULL)
      m_ItemParent = Item;

    PrevItem->Prev = Item;
    Item->Next = PrevItem;
  }

  return TRUE; // Return success
}

BOOL cCharICS::MoveDown(sCharItem *Item)
{
  sCharItem *NextItem;

  // Swap item and item after it
  if((NextItem = Item->Next) != NULL) {
    if((Item->Next = NextItem->Next) != NULL)
      NextItem->Next->Prev = Item;

    if((NextItem->Prev = Item->Prev) != NULL)
      Item->Prev->Next = NextItem;
    else
      m_ItemParent = NextItem;

    NextItem->Next = Item;
    Item->Prev = NextItem;
  }

  return TRUE; // Return success
}
#6
04/07/2005 (7:52 pm)
CharICS.cc - continued // character inventory control system
long cCharICS::GetNextLong(FILE *fp)
{
  char Buf[1024];
  long Pos = 0;
  int c;

  // Read until EOF or EOL
  while(1) {
    if((c = fgetc(fp)) == EOF)
      break;
    if(c == 0x0a)
      break;
    if((c >= '0' && c <= '9') || c == '.' || c == '-')
      Buf[Pos++] = c;
  }
  if(!Pos)
    return -1;
  Buf[Pos] = 0;

  return atol(Buf);
}

float cCharICS::GetNextFloat(FILE *fp)
{
  char Buf[1024];
  long Pos = 0;
  int c;

  // Read until EOF or EOL
  while(1) {
    if((c = fgetc(fp)) == EOF)
      break;
    if(c == 0x0a)
      break;
    if((c >= '0' && c <= '9') || c == '.' || c == '-')
      Buf[Pos++] = c;
  }
  Buf[Pos] = 0;

  return (float)atof(Buf);
}
#7
04/07/2005 (7:54 pm)
CharICS.h character inventory control system header.. struct for actual character inventory items
#ifndef _CHARICS_H_
#define _CHARICS_H_

typedef struct sCharItem
{
  long      ItemNum;          // Master-Item-List item number
  long      Quantity;         // Quantity of item (ie coins,arrows,bullets)

  sCharItem  *Prev, *Next;    // linked list pointers

  long       Index;           // This items index #
  long       Owner;           // Owner index # (player, npc, map)
  sCharItem  *Parent;         // Parent of a contained item (treasure chest)

  sCharItem()
  { 
    Prev = Next = Parent = NULL;
    Index = 0; Owner = -1;
   }
   
  ~sCharItem() { delete Next; } 
} sCharItem;

class cCharICS
{
  private:
    long      m_NumItems;     // # items in inventory
    sCharItem *m_ItemParent;  // Linked list parent item
    
    // Functions to read in next long or float # in file
    long  GetNextLong(FILE *fp);
    float GetNextFloat(FILE *fp);

  public:
    cCharICS();   // Constructor
    ~cCharICS();  // Destructor

    // Load, save, and free a list of items
    BOOL Load(char *Filename);
    BOOL Save(char *Filename);
    BOOL Free();

    // Add and remove an item
    BOOL Add(long ItemNum, long Quantity,    
             sCharItem *OwnerItem = NULL);
    BOOL Remove(sCharItem *Item);

    // Retrieve # items or parent linked list object
    long      GetNumItems();
    sCharItem *GetParentItem();
    sCharItem *GetItem(long Num);

    // Re-ordering functions
    BOOL Arrange();
    BOOL MoveUp(sCharItem *Item);
    BOOL MoveDown(sCharItem *Item);
};

#endif
#8
04/08/2005 (3:40 am)
Maybe post this as a resource rather than in the forums?
#9
04/08/2005 (1:18 pm)
Well its not complete, i'm hoping a few people will get this working with torque, then we could write up a resource and tutorial.
#10
04/08/2005 (1:20 pm)
C++ is definitely NOT the language for such a critter.
#11
04/08/2005 (1:35 pm)
Resource will be great...
#12
04/08/2005 (2:19 pm)
Why not? I'd think any large part of the game is better in C++ than script.
#13
04/08/2005 (4:53 pm)
Spocks is similar, using linked lists, and is in c++
#14
04/11/2005 (8:04 am)
This wouldn't work in a multiplayer environment, your client would be completely unaware of his inventory.

Best way to do inventory IMHO, is to manage it server side via script, and add a part to the inventory script, to notify the client via CommandToClient, having it store inventory in a global array, and tie the array to a GUI.

Easy, quick and can be implemented in < 30 mins except for the GUI which could take a little time to develop.

I have a whole series of tutorials coming up and one section deals with a complete inventory system that requires no tinkering under the hood.

Now if they will just get the things approved so I can finish the tuts :)