Inventory System help

Halas

Member
Hello,

Ever since the 2.3 update, I have been dealing with some issues regarding id and data structures.

When I equip an item, it reads the stats on the item and then transfers that data to the character stats.

This block of code identifies what item is picked up and which stats need to be applied.

GML:
//Update Stats
if ex_item_get_amount(_pressed_slot.inv,_pressed_slot.index) >= 1
    {
        var _item = _pressed_slot.item[? "category"]  
        show_debug_message(string(_item))
                //Items
               
        if _item = "spell"
            {
                ex_ev_subscribe(global.inv_equipment, id, Spells_roll)
                show_debug_message("Spells Roll")
            }
        if _item != "spell" && _item != "rune"  
            {
                ex_ev_subscribe(global.inv_equipment, id, Stats_roll)
                show_debug_message("Stats Roll")
            }      
        if _item = "rune"  
            {
                ex_ev_subscribe(global.inv_runespage, id, Runes_roll)
                show_debug_message("Runes Roll")
            }
    }

Once the correct Stats roll is selected, this script runs:

I notice this entire block of code is not currently running completely.

Code:
function Stats_roll()
{
    var _inv = global.inv_equipment;

    show_debug_message("Stats Roll Script")
        for (var a=0; a<5; a++)
                {
                    global.Stats[a] -= global.stats_items[a];
                    global.stats_items[a] = 0;
                    Obj_Player.weapon = 0;
                    Obj_Player.Idmg = 0;
                }
        //Check item and its Stats
        for (var _i = 0; _i < ex_inv_max_size(_inv); _i++)
            {
                if (ex_item_get_amount(_inv, _i)) >= 1
                    {
                        //Get Info
                        var _key = ex_tag_get(_inv,_i,"Key");
                        var _istat = ex_tag_get(_inv, _i, "Istat");
                       
                        var _ivalue = ex_tag_get(_inv, _i, "Ivalue");
                        if is_undefined(_ivalue) { return 0;};
                       
                        var _idmg = ex_tag_get(_inv, _i, "Damage");
                       
                        global.stats_items[_istat] += _ivalue;  
                        Obj_Player.Idmg = _idmg
                        Obj_Player.weapon = _key
                       
                        ex_ev_unsubscribe(_inv, _i);
                    }
                           
            }
        //Add Item Stats to Player Stats
        for (var a=0; a<5; a++)
            {  
                global.Stats[a] += global.stats_items[a]
            }
    }

Two scripts that I have identified that are causing the issue:

Previous to 2.3 Subscribe would collect the "id" of the object, and unsubscribe would also use "id"
If I use "id" I get this error:
___________________________________________
############################################################################################
ERROR in
action number 1
of Other Event: User Defined 2
for object obj_inv_panel:

Data structure with index does not exist.
at gml_Object_obj_inv_panel_Other_12 (line 28) - var _item = _pressed_slot.item[? "category"]
############################################################################################
gml_Object_obj_inv_panel_Other_12 (line 28)
gml_Object_obj_inv_panel_equipment_Other_12 (line 57)
gml_Object_obj_inv_mouse_Step_0 (line 9)
but removing "id" gets rid of this error but still causes issues in the code. I have since changed it to I use "_i" to try and specify the specific item to unsubscribe, but i do not believe it works properly as items no long register properly.

Code:
function ex_ev_subscribe(_inv, _instance, _script)
    {
        //var _inv = argument0;
        //var _instance = argument1;
        //var _script = argument2;

        //get list of subscribers for this event
        var _observers = _inv[? "observers"];

        //create observer array
        var _observer = array_create(2);

        //add instance
        _observer[0] = _instance;

        //add script
        _observer[1] = _script;

        //register
        ds_list_add(_observers, _observer);
    }


Code:
function ex_ev_unsubscribe(_inv, _instance) {

    //var _inv = argument0;
    //var _instance = argument1;

    //get list of subscribers for this event
    var _observers = _inv[? "observers"];

    //remove from the subscribers
    var _observers_count = ds_list_size(_observers);
    for(var _i = _observers_count - 1; _i >= 0; _i--) {
   
        var _observer = _observers[| _i];
   
        if(_observer[0] == _instance) {
            ds_list_delete(_observers, _i);
        }
    }
}
 

Nidoking

Member
Where is pressed_slot.item defined? The error message is telling you that there's no such map, or possibly that there's no entry for "category" in the map.
 

Halas

Member
It is set up during item creation and loses its tag once an item is unequipped.

I am usually able to equip it once and take it off. Also if I have another item equipped, and try to equip an item, it gives the same error.
 

Nidoking

Member
It is set up during item creation and loses its tag once an item is unequipped.
That's a cool story, but I don't see that in any of the code you posted.

I am usually able to equip it once and take it off.
This suggests that something involved in either the process of equipping or the process of taking it off is incorrect. Again, I see none of that here to begin to suggest what could be the problem.
 

Halas

Member
The item is created out of a CSV file.

WEAPONS
keystack_sizeimage_indexsprite_indextypecategoryname
stringrealrealassetstringstringstring
4​
1​
1​
spr_ex_item_weaponsweaponbowComposite bow
5​
1​
2​
spr_ex_item_weaponsweaponswordBronze Khopesh
6​
1​
3​
spr_ex_item_weaponsweaponswordIron Sword
7​
1​
4​
spr_ex_item_weaponsweaponaxeWooden Axe
SPELLS
keystack_sizeimage_indexsprite_indextypecategoryname
stringrealrealassetstringstringstringnotes
48​
1​
1​
spr_spell_iconspellspellfirefireball
49​
1​
2​
spr_spell_iconspellspelliceice shard
50​
1​
3​
spr_spell_iconspellspelllightlightning strike

pick_up = 0;

#region Item Select
rarity = choose(1,1,1,1,1,1,2,2,3)
switch (rarity)
{
case "1":
//normal
Irare = "Regular"
Iweight = random_range(8,9)
break;

case "2":
//magical
Irare = "Rare"
Iweight = random_range(7,8)
break;

case "3":
//rare
Irare = "Legendary"
Iweight = random_range(6,7)
break;
}


item_str = choose(1,1,1,2)
if item_str = 1 { item_id = irandom_range(1,5) }
else if item_str = 2 { item_id = irandom_range(1,15) }

image_index = item_id;
image_speed = 0;


// Set a random value & Stat
//Select Stat
if !item_id >= 12 {
Istat = irandom(4); }
else { Istat = 1; }
//Select Stat Value
if !item_id >= 7 && !item_id <= 11 {
Ivalue = irandom_range(1,3)*rarity }
else {
Ivalue = (irandom_range(1,3)+1)*rarity
}
//Select Damage
if rarity != 3 {
Idmg = irandom_range(15,25)*(1+(global.level/100))*(1+(rarity/100)) } else {
Idmg = irandom_range(25,30)*(1+(global.level/100)) }

// Set item Stats on Map
tags = ds_map_create();
tags[? "Rarity"] = Irare;
tags[? "Istat"] = Istat;
tags[? "Ivalue"] = Ivalue;
tags[? "Key"] = item_id;
tags[? "Weight"] = Iweight;
tags[? "Damage"] = Idmg;

#endregion

//Mouse Location
mouse_x_gui = device_mouse_x(0);
mouse_y_gui = device_mouse_y(0);

//Gravity
vsp = 0;
grv = 0.2;

But it unlikely the issue. I tested this already with a show debug message, when I select an item, it correctly names its category. That is until I equip and unequip the item, where the Subscribe and Unsubscribe scripts come into play.

I do believe the issue lies in the Subscribe and Unsubscribe scripts.
 

Nidoking

Member
Glad to hear you've apparently found the problem and no longer need help with it.

Meanwhile, I don't see anywhere that either your subscribe or unsubscribe function populates or depopulates _pressed_slot.item, so I believe you are wrong in your assumptions and are simply failing to provide the information that would allow anyone to help you. If you would like help, kindly finish asking the question as I have requested.
 

Halas

Member
Here is the entire block of code:
Let me know what else you require.

GML:
/// @desc on mb left

/*
    Called by obj_inv_mouse when a slot of the panel is left pressed
*/

var _pressed_slot = other.id;
var _mouse_slot = obj_inv_mouse.slot;


//if items in mouse and pressed slot are the same, try adding
if(ex_ui_slot_compare(_mouse_slot, _pressed_slot))
{
    ex_item_move(_mouse_slot.inv, _mouse_slot.index, inv, _mouse_slot.amount, _pressed_slot.index);
}

//otherwise if the items are different, simply switch
else {
    ex_item_switch(inv, _pressed_slot.index, _mouse_slot.inv, _mouse_slot.index);
}

//if inv = global.inv_toolbar or global.inv_mouse
//{ ex_saveInv(); }

//Update Stats
if ex_item_get_amount(_pressed_slot.inv,_pressed_slot.index) >= 1
    {
        var _item = _pressed_slot.item[? "category"]   
        show_debug_message(string(_item))
                //Items
                
        if _item = "spell"
            {
                ex_ev_subscribe(global.inv_equipment, id, Spells_roll)
                show_debug_message("Spells Roll")
            }
        if _item != "spell" && _item != "rune"   
            {
                ex_ev_subscribe(global.inv_equipment, id, Stats_roll)
                show_debug_message("Stats Roll")
            }       
        if _item = "rune"   
            {
                ex_ev_subscribe(global.inv_runespage, id, Runes_roll)
                show_debug_message("Runes Roll")
            }
    }
 

Nidoking

Member
What surrounds this? The first thing you do is other.id, so it must either be inside a with or a collision event.
 

Halas

Member
The above code is: on_mb_left
and is triggered by the mouse step event.





GML:
///@desc handle mouse events

if(slot_current == noone) { exit; }

//if currently hovering a slot, check for mouse events
with(slot_current) {

    if(mouse_check_button_pressed(mb_left)) {
        with(panel) { event_user(INV_PANEL_EVENTS.on_mb_left); }
    }
    else if(mouse_check_button_pressed(mb_right)) {
        with(panel) { event_user(INV_PANEL_EVENTS.on_mb_right); }
    }
 
}

slot_current = instance_position(mouse_x_gui, mouse_y_gui, obj_inv_slot);

///@desc refresh event

/*
This event refreshes the slot, and has is called when the slot item in the inventory gets an update.
*/

//refresh slot properties
item = ex_item_get_item(inv, index);
key = ex_item_get_key(inv, index);
stack_id = ex_item_get_stack_id(inv, index);
amount = ex_item_get_amount(inv, index);

//if the item had tags before the update, destroy the ds_map and get the new one
if(tags >= 0) { ds_map_destroy(tags); }
tags = ex_item_get_tags(inv, index);

//update sprite
if(item < 0) {
sprite_index = default_sprite;
}
else {
sprite_index = item[? "sprite_index"];
image_index = item[? "image_index"];
}
 

Nidoking

Member
Well, I don't know about calling event_user inside a with, but just using other straight up in a left mouse button event is not going to work. But does ex_item_get_tags create a new ds_map each time you call it? That's another function you've called but not included the code for.
 

Halas

Member
GML:
function ex_item_get_tags(argument0, argument1) {

    //get the value of the tags column
    var _tags = _ex_fn_item_get(argument0, argument1, EX_COLS.tags);

    if(_tags >= 0) {
        _tags = _ex_fn_ds_map_dup(_tags);
    }

    return _tags;
}
Code:
function _ex_fn_ds_map_dup(argument0) {

    var _copy = ds_map_create();
    ds_map_copy(_copy, argument0);

    return _copy;

}
Code:
function _ex_fn_item_get(argument0, argument1, argument2) {

    var _inv = argument0;
    var _slot = argument1;
    var _attr = argument2;

    var _items = _inv[? "items"];

    return _items[# _attr, _slot];


}
 

Nidoking

Member
var _tags = _ex_fn_item_get(argument0, argument1, EX_COLS.tags);
Okay, this is all getting to be a tangled mess of wrappers that I don't see the reason for, but isn't this just getting the tags map from the item? Which you destroyed before calling this function? So you're trying to copy the destroyed map to replace the map you destroyed? Maybe there's something fundamental about your inventory system that I'm not understanding.

Maybe try putting a breakpoint inside the block for if(_tags >= 0) and see whether it actually reaches that point when you're having the problem. I suspect that it doesn't.
 

Halas

Member
If I run in the debugger and put a breakpoint where you said, the game stops on startup.

ex_item_get_tags - returns the tags at the given slot index so it can compare if the item can be placed or if the item has other tags - such as a helm going to the head slot, or the value of damage this particular item has.
To do so, it will create a DS map of the tags.

Items are held in a DS List
GML:
function ex_db_add() {

    var _item = argument[0];
    var _key = _item[? "key"];

    //check for errors
    if(!ds_map_exists(_item, "key") || !ds_map_exists(_item, "stack_size")) {
        show_debug_message("!ERROR! ex_db_add: Trying to add an items without a key or stack_size attribute");
        exit;
    }

    if(ds_map_exists(global._ex_db, _item[? "key"])) {
        show_debug_message("!ERROR! ex_db_add: An item with the same key exists in the database");
        exit;
    }

    //add to database
    ds_map_add_map(global._ex_db, _key, _item);

    //add key to master list
    ds_list_add(global._ex_db_keys, _key);

    //add key to group, creating one if it doesn't exist
    if(argument_count > 1) {
        var _group_name = argument[1];
        var _group = global._ex_db_groups[? _group_name];
    
        if(is_undefined(_group)) {
            _group = ds_list_create();
            ds_map_add_list(global._ex_db_groups, _group_name, _group);
        }
    
        ds_list_add(_group, _key);
    }


}
 
Last edited:

Nidoking

Member
If I run in the debugger and put a breakpoint where you said, the game stops on startup.
Stops and crashes, or stops at the breakpoint until you hit the Continue button? It shouldn't be crashing in the debugger if it runs normally, although I've had some issues with that in 2.3. If necessary, put a show_debug_message instead, which will display in your Output window even if you run it normally from within Game Maker.
 

Nidoking

Member
OK, so when you hit the Continue button and run the game until you see the problem... does it hit the breakpoint while the problem is happening?
 

Halas

Member
OK, so when you hit the Continue button and run the game until you see the problem... does it hit the breakpoint while the problem is happening?
It stops every time I interact with the inventory and with the items. So constantly stopping.


I changed the following and the error seems to have changed the issue:

In the Subscribe instance arg I put "other.id" and the error is gone, but, the Stats_roll and Spells_roll scripts are not fully executing properly. The Stats_roll is running even when I equip a Spell item

GML:
if ex_item_get_amount(_pressed_slot.inv,_pressed_slot.index) >= 1
    {
        var _item = _pressed_slot.item[? "category"] 
        show_debug_message(string(_item))
                //Items
              
        if _item = "spell"
            {
                ex_ev_subscribe(global.inv_equipment, other.id, Spells_roll)
                show_debug_message("Spells Roll")
            }
        if _item != "spell" && _item != "rune" 
            {
                ex_ev_subscribe(global.inv_equipment, other.id, Stats_roll)
                show_debug_message("Stats Roll")
            }     
        if _item = "rune" 
            {
                ex_ev_subscribe(global.inv_runespage, other.id, Runes_roll)
                show_debug_message("Runes Roll")
            }
    }
 

Nidoking

Member
I still have no idea what "other" is supposed to be in this context. You've posted so many random bits of code here and there that I don't know how anyone's supposed to follow it at this point, and I don't know from your comment whether you're saying the problem is that you're seeing the wrong debug message or that it's running the wrong function. You're going to need to learn to use the debugger to solve your own problems here.

It stops every time I interact with the inventory and with the items. So constantly stopping.
You can disable the breakpoint until you get to the point where you expect to see the problem, then enable it. If you go changing things that don't solve the problem, and instead invent new problems, then the entire thread is meaningless and might as well just be thrown out.
 

Halas

Member
I changed the code back to what it was and ran the debugger - It stopped right before the error. So once I hit continue, the error pops up.

Other refers to the inv slot the mouse is interacting with.
 

Halas

Member
GML:
/// @desc on mb left

/*
    Called by obj_inv_mouse when a slot of the panel is left pressed
*/

var _pressed_slot = other.id;
var _mouse_slot = obj_inv_mouse.slot;


//if items in mouse and pressed slot are the same, try adding
if(ex_ui_slot_compare(_mouse_slot, _pressed_slot))
{
    ex_item_move(_mouse_slot.inv, _mouse_slot.index, inv, _mouse_slot.amount, _pressed_slot.index);
}

//otherwise if the items are different, simply switch
else {
    ex_item_switch(inv, _pressed_slot.index, _mouse_slot.inv, _mouse_slot.index);
}

//if inv = global.inv_toolbar or global.inv_mouse
//{ ex_saveInv(); }

//Update Stats
if ex_item_get_amount(_pressed_slot.inv,_pressed_slot.index) >= 1
    {
        var _item = _pressed_slot.item[? "category"]  
        show_debug_message(string(_item))
                //Items
               
        if _item = "spell"
            {
                ex_ev_subscribe(global.inv_equipment, id, Spells_roll)
                show_debug_message("Spells Roll")
            }
        if _item != "spell" && _item != "rune"  
            {
                ex_ev_subscribe(global.inv_equipment, id, Stats_roll)
                show_debug_message("Stats Roll")
            }      
        if _item = "rune"  
            {
                ex_ev_subscribe(global.inv_runespage, id, Runes_roll)
                show_debug_message("Runes Roll")
            }
    }

This is the User Event in the OBJ_PANEL that we are currently looking at.
This event is triggered by OBJ_MOUSE - Step event.
GML:
///@desc handle mouse events

if(slot_current == noone) { exit; }

//if currently hovering a slot, check for mouse events
with(slot_current) {

    if(mouse_check_button_pressed(mb_left)) {
        with(panel) { event_user(INV_PANEL_EVENTS.on_mb_left); }
    }
    else if(mouse_check_button_pressed(mb_right)) {
        with(panel) { event_user(INV_PANEL_EVENTS.on_mb_right); }
    }
   
}

So the "other" is the panel (Inventory Slot) that the mouse is interacting with.

I think this may be too much to explain, you would have to actually go through the entire inventory system.
 

Nidoking

Member
other is "slot_current". The "panel" variable is self in that event. But I don't know what either a slot_current or a panel is to know which one should be subscribing to what. I think you're right. I would need to see the entire inventory system at once to figure this out. You still need to learn to use the debugger, though. That's the easiest route to your solution.
 
Top