Introduction

About

exINV is a general purpose inventory system that provides ready to use assets to manage an item database, multiple inventories and a comprehensive user interface built on top of that, including features like crafting and equipment selection and saving / loading mechanisms.

exINV can be seen as a collection of three main modules: Database system, Inventory system and User Interface & Logic (GUI).

It is important to understand that while the Database system & the Inventory system are generic enough to be used as they are in any project, the GUI and its logic may differ greatly from one project to another, depending on the desired result.
The UI and the other modules are, however, totally decoupled. This means that you can safely import all the scripts into any project and they'll work out of the box, since they do not reference any pre-defined object.
The provided UI instead is a structured and commented example built on top of that; you will need to edit and adapt it to your needs, or simply start from scratch and build your own.

Getting started

In order to integrate the inventory system into an existing project, you have to follow a few steps:

1. Import the resources

It is strongly suggested to import all of the asset resources into your project.
Once you are familiar with how everything works you may start removing the objects and functionalities you don't need.
If you only want the inventory and database management (no GUI), you only need to import the scripts in the ex folder and the included files.

2. Initialize the system

The first thing you have to do is initialize the inventory system. You should only do that once, ideally at game start. In the sample project this is done in obj_init_rm_controller, in your project you should add this code in your game initialization routine.

//init and load the database from the provided csv files
ex_init();
ex_db_load("inv_armor.csv","inv_food.csv","inv_potions.csv","inv_weapons.csv");

//initialize inventory events for the user interface.
inv_events_init();

//initialize the crafting system
craft_init();

3. Creating and displaying the player inventories

Once the inventory system is initialized, you may start creating inventories. The sample player inventories, composed of bag, equipment, toolbar and crafting bench are generated by an event in obj_inv_controller.

obj_inv_controller has to be present in every room that needs to display the player inventory, and is in charge of creating and keeping references to all the inventories assigned to your player, as well as the mouse inventory, used to temporarily store items being moved from one slot to another.

The room controller (obj_inv_rm_controller) creates and displays the player inventories at room start. Since you are probably using your own game room controller, be sure to run this code in there.

with(obj_inv_controller) {
//create new inventories
event_user(inv_controller_events.create_inventories);

//open all the panels
event_user(inv_controller_events.toggle_bag);
event_user(inv_controller_events.toggle_crafting);
event_user(inv_controller_events.toggle_equipment);
event_user(inv_controller_events.toggle_toolbar);
}

The "create_inventories" event generates a bag, equipment, crafting and toolbar inventories. As an example, here's the code to create and display the bag inventory with some random items:

//get all the items keys to be used to generate some random items
var db_keys = ex_db_keys();

//create an inventory with 30 slots
bag_inv = ex_inv_create(30);

//fill some slots with random items. This is all in memory, no visual representation is created up to this point
for(var i=0; i<30; i++) {
var item = choose("",db_keys[| irandom(ds_list_size(db_keys)-1)]); ex_inv_item_add(bag_inv,item,irandom(64),i); }

//display the bag inventory using an instance of obj_inv_bag
inv_panel_show(bag_inv,obj_inv_bag,96,160,depth);

You are strongly encouraged to go through every event of obj_inv_controller, since this is where most of the initialization and player inventory structure is defined.

It is also worth noting that inventories are like data structures, while the GUI instances are destroyed at room end, the inventory itself lives in the program memory until ex_inv_destroy(inventory) is called.
You should therefore avoid creating new player inventories from scratch in every room, just be sure to do so in the first room of a new game, or you'll run out of memory.

Database system

Introduction

The database stores a collection of all the items in your inventory system along with their properties, and allows a fast and easy access to this data. It is worth noting that database data should be considered read-only. Items inside an inventory can still be assigned individual attributes via tags.

Data files

Database data is stored in one or more CSV files located in the included files of your project. They can be loaded using ex_db_load(file1,file2,...). Each file can define different properties for the items it contains, but there are some requirements:

  • The first row holds the property names.
  • The second row holds the types, either string or real, defining the type of data.
  • You are required to define a column key of type string, holding a unique (among all files!) identifier per item
  • You are required to define a column stack_size of type real, holding the maximum stack size for the item (1 for non stackable items)

Please refer to the included sample files for an example

Database structure

The database itself is actually a ds_map holding other ds_maps as items, referenced by the item key.
Along with the database, a ds_list of all keys is also generated and accessible with ex_db_keys(). This can be useful for example when you need to get a random item, as it's enough to get a random element from the list.

Scripts reference

ex_db_get(key)

Returns a ds_map holding all the data of the item with the specified key.

var item = ex_db_get("weapon_fire_arrow"); //get the fire arrow item data
var item_name = item[? "name"]; //get the name property of the item
ex_db_keys()

Returns a ds_list where all database keys are stored. This can be useful for example for selecting a random item, or determining the size of the database.

var keys = ex_db_keys();
var keys_count = ds_list_size(keys);
var random_item_key = keys[| irandom(keys_count)-1];
ex_db_load(file1,file2,...)

Loads data from one or more CSV files (as described above) into the database.

ex_db_load("inv_armor.csv","inv_food.csv","inv_potions.csv","inv_weapons.csv");

Inventory system

Introduction

Inventories are data structures that reference database items and are, therefore, stored and kept in memory as long as the program is running or the inventory explicitly destroyed with ex_inv_destroy();

While inventories are meant to be displayed and interact with the player input, in exINV they are not tied to a visual representation, since that's the GUI role. You can create and perform all operations on inventories without displaying them.

The only exception to the above is represented by the script ex_inv_updated, which is called every time an inventory changes, and should include the code required to update the visual representation of the given inventory.

Inventories

An inventory is a ds_map holding the following data:

max_size: the number of slots of the inventory
size: the amount of non empty slots
items: a ds_grid holding the keys of all the items in the inventory slots, as well as the respective amount per slot. You shouldn't work directly with the the items grid, but you may need to understand its structure for customization purposes. The items grid has the following columns (in order): slot index, item key, amount, stack id (determines how items stack), item id (reference to the database), tags.

The following example outputs the number of free slots of an inventory to the console:

var free_slots = inv[? "max_size"] - inv[? "size"];
show_debug_message("The inventory has " + string(free_slots) + " free slots");

Scripts reference

ex_inv_clear(inv)

Clears an inventory by deleting all its contents

inv {integer} inventory to clear

ex_inv_clear(equipment_inv);
ex_inv_create(size)

Creates a new empty inventory

size {integer} number of slots

returns {ds_map} the inventory ds_map (to be passed to other functions)

equipment_inv = ex_inv_create(6);
ex_inv_destroy(inv)

Destroys an inventory and all its contents. This generates an inventory_destroyed event for the related GUI panels, automatically closing them.

inv {integer} inventory to destroy

ex_inv_destroy(equipment_inv);
ex_inv_read(data)

Reads and returns an inventory from a string, as returned by ex_inv_write

data {string} string containing inventory data

returns {ds_map} the inventory ds_map

equipment_inv = ex_inv_read(save_data);
ex_inv_resize(inv,new_size)

Resizes an inventory by adding or removing slots according to the new size. Items in extra slots are removed.
This function generates an inventory_resized event on its open GUI panels. You should implement this event according to your inventory behavior

inv {integer} inventory to resize
new_size {integer}

ex_inv_resize(toolbar_inv,8);
ex_inv_sort(inv,asc)

Sorts the inventory based on the item keys.

inv {integer} inventory to sort
asc {boolean} if true, sorts in ascending order, descending otherwise

ex_inv_sort(toolbar_inv,true);
ex_inv_updated(event,[affected_slots])

This script gets called whenever an inventory changes in some way. You should not call this script directly.
If you are implementing your own GUI on top of exinv, you should adapt this script to suit your needs.

event {integer} update event (from ex_events enum)

[affected_slots] {ds_list} list of slots affected by the update (only for ex_events.slots_updated)

equipment_inv = ex_inv_create(6);
ex_inv_write(inv)

Returns a JSON encoded string of the specified inventory.

inv {integer} inventory to encode

returns {string} a JSON string representation of the provided inventory

//save the equipment inventory to file

var data = ex_inv_write(equipment_inv);

var file = file_text_open_write("equipment.inv");
file_text_write_string(file,data);
file_text_close(file);

Inventory items

As mentioned above, an item inside an inventory slot is a collection of several things:

  • amount: the amount of the same item in the slot
  • key: the key of the item in the slot
  • item: a reference to the item data (a ds_map) in the database
  • stack_id: a string used to test item stacking (more on that in the tags section)
  • tags: a ds_map holding the specific item tags (if any)

You can access those properties at any time using the ex_inv_item_* functions.

It is important to note that every item of the same type (that is, having the same key), references the same data in the database. You should not change the item data in any way, as this would change the database directly. If you want to give an item some extra attributes, you should use tags.

Unique items

Unique items are essentially regular items having a unique key. Unique items are not considered as items of their base type by functions like find, count, remove, etc..., and will not stack with other items of the same base type.

To mark an item as unique, you'll have to call ex_inv_item_set_unique on an inventory slot. This will mark all items in that particular slot stack as unique.

It is worth noting that unique items have keys that are not present in the database. You can still get the database data of a unique item by using ex_in_item_data(), which will also allow you to know their base key.

Scripts reference

ex_inv_item_amount(inv,slot)
ex_inv_item_data(inv,slot)
ex_inv_item_key(inv,slot)
ex_inv_item_stack_id(inv,slot)
ex_inv_item_tags(inv,slot)

Returns an attribute at the given slot index of the specified inventory.

inv {integer} inventory to look into
slot {integer} slot index in the inventory

returns the attribute at the specified slot. Can be one of the following:
amount: {integer} amount of items in the slot (0 if slot is empty)
data: {ds_map} reference to the item data in the database (-1 if slot is empty)
key: {string} item key ("" if slot is empty)
stack_id: {string} stack id (see tags section)
tags: {ds_map} tags ds_map (-1 if slot is empty or no tags defined)

var item = ex_inv_item_data(toolbar_inv,0);
if(item >= 0) {
show_debug_message("Name of the item in slot 0: " + item[? "name"]); }
ex_inv_item_add(inv,key,amount,[slot],[tags])

Inserts an item into an inventory in the first available slot(s), or in the specific slot.

inv {integer} inventory
key {string} key of the item to insert
amount {integer} amount of items to insert
[slot] {integer} (optional) slot to insert the item(s) into. Defaults to -1 if not set.
[tags] {integer} (optional) tags to assign to the item, as a ds_map.

returns {integer} the number of items inserted (same as amount given enough space in the inventory or slot)

IMPORTANT: you can optionally assign tags to an item directly, by passing a ds_map of tags to this function. This specific map is then used as tags, meaning you are not supposed to destroy it after calling this function, UNLESS the insertion fails (e.g: the inventory is full), in which case what happens to the ds_map is up to you.

var n = ex_inv_item_add(toolbar_inv,"food_carrot",16,3);
show_debug_message(string(n) + " carrots have been inserted in slot 3 of the toolbar!");
ex_inv_item_copy(inv1,slot1,inv2,amount,[slot2])

Copies an item from one slot to another. inv1 and inv2 can be the same inventory or a different one.

inv1 {integer} inventory to copy the item from
slot1 {integer} slot index in inv1
inv2 {integer} inventory to copy the item to
amount {integer} amount to copy. If se to -1, all items in slot 1 are copied
[slot2] {integer} (optional) slot index in inv2. If omitted, the item will be copied in the first available slot.

returns {integer} the amount of items actually copied.

ex_inv_item_copy(toolbar_inv,0,bag_inv);
ex_inv_item_find(inv,key,ex_find_type)

Returns the amount of items in an inventory matching the given key.

inv {integer} inventory
key {string} key of the item to look for
ex_find_type {integer} the type of find operation to apply, from the ex_find_type enum: ex_find_type.first, ex_find_type.last, ex_find_type.low (lowest amount), ex_find_type.high (highest amount)

returns {integer} the slot index of the first item found, or -1 if nothing is found.

//removes all items from the first slot of the toolbar holding carrots (if any)
var slot = ex_inv_item_find(toolbar_inv,"food_carrot",ex_find_type.first);
if(slot >= 0) {
ex_inv_item_remove(toolbar_inv,"",-1,slot); }
ex_inv_item_move(inv1,slot1,inv2,amount,[slot2])

Moves an item from one slot to another. inv1 and inv2 can be the same inventory or different ones.

inv1 {integer} inventory to move the item from
slot1 {integer} slot index in inv1
inv2 {integer} inventory to move the item to
amount {integer} amount to move. If se to -1, all items in slot 1 are moved
[slot2] {integer} (optional) slot index in inv2. If omitted, the item will be moved in the first available slot.

returns {integer} the amount if items actually moved.

//copy all items in slot 0 of the toolbar to the bag inventory
ex_inv_item_move(toolbar_inv,0,bag_inv,-1);
ex_inv_item_remove(inv,key,amount,[slot])

Removes an item(s) from an inventory, or a specific slot

inv {integer} inventory
key {string} key of the item(s) to remove
amount {integer} amount of items to remove. If set to -1, removes all items from a slot (if specified), or all items having that specific key in the inventory.
[slot] {integer} (optional) slot to remove the item(s) from

returns {integer} the amount if items actually removed

var n = ex_inv_item_remove(toolbar_inv,"food_carrot",8);
show_debug_message(string(n) + " carrots have been removed from the toolbar!");
ex_inv_item_set(inv,key,amount,slot)

Inserts an item into an inventory slot, replacing any previous content.

inv {integer} inventory
key {string} key of the item to set
amount {integer} amount of items to add
slot {integer} slot to set items into

returns {integer} the amount if items actually inserted

//replace the contents of the first slot of the toolbar with a carrot
ex_inv_item_set(toolbar_inv,"item_carrot",1,0);
ex_inv_item_set_unique(inv,slot,new_key)

Mark an item (or stack) as unique. Unique items are not considered as items of their base type by functions like find, count, remove, etc..., and will not stack with other items of the same base type.

inv {integer} inventory
slot {integer} slot index
new_key {string} new key to assign to the item

returns {boolean} true if successul

ex_inv_item_set_unique(toolbar_inv,0,"unique_item_1");
ex_inv_item_switch(inv1,slot1,inv2,slot2)

Swaps two items at the specified slots (either in the same inventory or different ones)

inv1 {integer} first inventory
slot1 {integer} slot in the first inventory
inv2 {integer} second inventory
slot2 {integer} slot in the second inventory

//switch the first two slots in the toolbar
ex_inv_item_switch(toolbar_inv,0,toolbar_inv,1);
ex_inv_item_test(inv,key,amount,[slot],[tags])

Works like ex_inv_item_add(), but doesn't actually add any items to the inventory, simply returning the amount of items that could in theory be inserted using an add operation.

inv {integer} inventory
key {string} key of the item to insert
amount {integer} amount of items to insert
[slot] {integer} (optional) slot to insert the item(s) into. Defaults to -1 if not set.
[tags] {integer} (optional) tags to apply to the item

returns {integer} the number of items that could be inserted by an add operation.

IMPORTANT: you can pass a ds_map as item tags to this function as in ex_inv_item_add. The map is not destroyed automatically after the call, so it's up to you to free it if no longer needed.

var n = ex_inv_item_test(toolbar_inv,"food_carrot",16,3);
show_debug_message(string(n) + " carrots can safely be added to slot 3 in the toolbar");
ex_inv_item_test_ext(inv1,slot1,inv2,amount,[slot2])

Works like ex_inv_item_copy(), but doesn't actually copy any items to the inventory, simply returning the amount of items that could in theory be inserted using a copy operation.

inv1 {integer} inventory to copy the item from
slot1 {integer} slot index in inv1
inv2 {integer} inventory to copy the item to
amount {integer} amount to copy. If se to -1, all items in slot 1 are copied
[slot2] {integer} (optional) slot index in inv2. If omitted, the item will be copied in the first available slot.

returns {integer} the amount if items that could be inserted by a copy operation.

var n = ex_inv_item_test_ext(bag_inv,0,toolbar_inv,-1);
show_debug_message(string(n) + " items can safely be copied from slot 0 in the bag to the toolbar");

Tags

As stated in the database section, item properties are to be considered immutable. An item having a certain key shares the same attributes as all the other items having that same key.
Tags on the other hand, allow the definition of extra properties assigned to a specific item or stack of items in an inventory.

While there are a number of functions to deal with tags, you should also keep in mind that tags are simply ds_maps. You are free to read them as you normally would using tags[? "property"], but you should avoid altering the ds_map directly in any way, due to stacking.

Tags vs stacking

With the introduction of tags, you may be wondering how stacking works between items having different tags.

In exinv, items will stack only if they have the same key AND the same tags. Comparing ds_maps though can be quite slow in terms of performance, therefore items are assigned a stack_id which is used as comparison instead of the ds_map to determine if items should be able stack. A stack_id is generated by the script _ex_generate_stack_id whenever a tag is added, removed or changed.

Scripts reference

ex_tag_delete(inv,slot,tag_name)

Deletes the tag from the item in the given slot

inv {integer} inventory
slot {integer} slot index
tag_name {string} tag to remove

ex_tag_remove(equipment_inv,0,"enchant");
ex_tag_exists(inv,slot,tag_name)

Returns true if the tag with the given name exists

inv {integer} inventory
slot {integer} slot index
tag_name {string} tag name

returns {boolean}

if(ex_tag_exists(equipment_inv,0,"enchant")) {
var enchant = ex_tag_get(equipment_inv,0,"enchant");
show_debug_message("Active equipment enchantment: " + enchant);
}
ex_tag_get(inv,slot,tag_name)

Returns the tag with the given name from the inventory slot

inv {integer} inventory
slot {integer} slot index
tag_name {string} tag to return

returns the tag value

//gets a tag holding the item durability in the first slot of the toolbar, and if the item is worn out, removes it from the inventory.
var durability = ex_tag_get(toolbar_inv,0,"durability");
if(durability == 0) {
ex_inv_item_remove(toolbar_inv,"",-1,0); }
ex_tag_set(inv,slot,tag_name,value)

Sets the tag with the given name, replacing the value of any existing tags with the same name, or creating a new one.

inv {integer} inventory
slot {integer} slot index
tag_name {string} tag to return
value {any} value to set

var durability = ex_tag_get(toolbar_inv,0,"durability");
ex_tag_set(toolbar_inv,0,"durability",durability-1);
ex_tag_size(inv,slot)

Returns the number of tags for the item in the given slot

inv {integer} inventory
slot {integer} slot index

returns {integer}

User Interface

Introduction

The user interface determines how inventories are displayed to the user, and all the required logic that handles how the inventory behaves when the user interacts with them.

As such, if the inventory and database system are generally project agnostic, the UI is instead an opinionated resource, and differs from game to game. exINV shows a way to program a GUI for some common inventories like crafting, player inventory, toolbar and equipment, but it's up to the programmer to refine or rewrite this part based on his needs.

It is important to understand that the inventory itself and the GUI are totally decoupled, meaning that there is no reference to objects or instances inside ex_inv scripts, with the notable exception of ex_inv_updated . This is the script that ties GUI and Inventories, and is in charge of notifying the GUI that something changed inside the inventories.

Panels

Panels are a visual representation of an inventory, and hold a number of slot instances linked with it showing the items in the inventory it is linked to.

Every inventory requiring a visual representation defines its own panel object, having obj_inv_panel as parent. A crafting inventory for example could be associated to a obj_inv_crafting panel, while a chest to obj_inv_chest panel, defining their own specific behaviour (if required).
Note that obj_inv_panel is never instantiated directly, but simply acts as a parent to the actual panel implementations.

A panel has always the following variables:

inv: The inventory referenced by the panel
slots: An array of slot instances that matches the size of the slots in inv

Panels can be created and destroyed freely without affecting the inventory. The provided scripts inv_panel_show() and inv_panel_hide() do in fact destroy and recreate a panel every time respectively. This means that a hidden panel is not just visually hidden, but doesn't exist at all as long as inv_panel_show() is called again, freeing resources from your game while closed.

Panels events

Panels have a number of events (defined as user events) that are triggered as the inventory changes or the user interacts with it or its slots. Those are defined in the script inv_events_init().
A panel needing a specific action for an event should override (define) that event by creating the right user event, replacing the default behavior define by obj_inv_panel.

after create (event_user_0): Called right after the panel is created. Slot instances are to be created in this event.
inventory changed (event_user_1): called after the inventory changes in some way. This is used in obj_inv_equipment to update the armor rating whenever something happens to the inventory, or in the crafting inventory to test if there's a matching recipe.
slot left pressed (event_user_2): called when a slot is left clicked. In this event, you can use other to refer to the specific slot
slot right pressed (event_user_3): called when a slot is right clicked. In this event, you can use other to refer to the specific slot
slot updated (event_user_4): called when a slot is updated. In this event, you can use other to refer to the specific slot
inventory resized (event_user_5): called when the inventory gets resized by ex_inv_resize
inventory destroyed (event_user_6): called when the inventory gets destroyed by ex_inv_destroy
panel cleanup (event_user_15): called when the panel instance is destroyed or the room changes

Extending panels events

Panel events are meant to be extended based on the functionalities you need. If, for example, you want something to happen when the middle mouse button is pressed on a slot, you'll have to:

  • 1. add the event name to the panel_events enum in the inv_events_init script, assigning the event user number you want to associate it to. Something like slot_middle_pressed = 7
  • 2. Add a middle mouse pressed event to obj_inv_slot that calls the event for the panel: with(panel) {event_user(panel_events.slot_middle_pressed)}
  • 3. Add an event user 7 to obj_inv_panel, and add the code that should be executed when this event gets called. If the code is not common to all or most of the panels, leave it blank and implement it in the specific panels that need it.

Slot instances

Slot instances are tied to a specific panel and a specific inventory. They are used to diplay the item held in a certain slot of an inventory, as well as detect user interaction (left click, right click, etc.).
Slot instances hold the following variables:

You are expected to generate your own slots on every panel you define, inside the after create event of that panel. Slots can be created using the inv_slot_create script. Look at the provided panels for examples on how to define slots having different interactions.

item: Reference to the database ds_map holding the item properties (-1 if empty)
key: Key of the item currently in the slot ("" if empty)
amount: Number of items in the stack
rel_x: x position of the slot relative to the panel
rel_y: y position of the slot relative to the panel
panel: Rerefence to the panel holding the slot instance
inv: Reference to the inventory
index: Index of the slot in the inventory

It is important to understand that slots are slaves with respect to inventory data, changing for example the item, amount or key variables of the slot instance will do absolutely nothing to the inventory itself.
Slots are automatically updated whenever the inventory changes.

Inventory controller

obj_inv_controller is in charge of the player inventory management. It creates and keeps a reference of all the inventories related to the player, providing some generic functionality like deleting all inventories, or toggle a specific panel.

obj_inv_controller also defines the mouse inventory. When the player is moving items around, the items being moved are stored temporarily in a special inventory having a single slot, tied to the mouse coordinates. Two scripts help getting information about the mouse inventory: inv_mouse_get and inv_mouse_get_slot, returning the mouse inventory and the slot instance respectively.

Crafting

Crafting is a broad term referring to in game item creation, generally by consuming other items. There is no single crafting system nor implementation that fits every game, therefore the provided cafting system is simply an example of how this can be achieved using exinv.

Crafting recipes are defined at game start by the script craft_init(), which generates a recipes database, each recipe holding the required items and the resulting item (as well as its quantity).

The panel obj_inv_crafting is in charge of checking the recipes and eventually produce the resulting item, by consuming the recipe items.
It defines 4 slots are dedicated to ingredients, and a special slot where the resulting items are generated. The panel defines a very specific logic for the result slot, prohibiting anything from being inserted there and consuming ingredients when the player actually get something out of it.

obj_inv_crafting also generates and keeps an updated ds_list of item keys based on the 4 ingredient slots, which is then sorted and compared to all recipe ingredient lists whenever an item is inserted or removed from the ingredient slots. By sorting both recipe and ingredients lists, we ensure that the order of the items plays no role in determining if a recipe is valid.

Keep in mind that this approach is relatively simple, but also limited in scope. It does not consider, in contrast for example with minecraft, the number of ingredient items in the same slot, nor the position in the crafting grid

Previous versions

exINV 1.3.0 documentation

Credits

Contact me on the YoYoGames forums or by email at simoneguerra<at>ekalia.com