GML DS Grid returning incorrect values

K

KiD_Rager

Guest
Hey folks!

I'm using the Inventory System by Beyond Us Games that mostly involves a variable called itemSelected. Since the project runs on DS grids, I have virtually no problem using accessors and whatnot to retrieve certain cell data, except...

My core issue involves retrieving certain data of one inventory's cell based on the name being pulled from another inventory's cell (two DS grids). Look at the drawn strings in the top left that show Grid info.



As you can see, when Short Sword is selected (not used btw) in the inventory, it says that the next upgrade name is Long Sword. Seems right. But selecting any other weapon doesn't change this.



To reference, I have two inventory lists: inv (main inventory) and invSword (list of all swords in the game for checking purposes).

inv - Added weapons
GML:
//Grid, Name, Lvl, Desc, Sprite, Type, Script, ID
scr_itemAdd(inv, "Short Sword", 1, "Basic sword.", spr_wepSword_ShortSword, "sword", wpnEquip, 1, 0);
scr_itemAdd(inv, "Long Sword", 1, "Long sword. Very good reach.", spr_wepSword_ShortSword, "sword", wpnEquip, 1, 532);
scr_itemAdd(inv, "Royal Sword", 1, "Special sword designed for royal knights.", spr_wepSword_ShortSword, "sword", wpnEquip, 1, 875);
invSword - Sword list
GML:
///Master swords list
//Grid, Name, Upg A, Upg Lvl A, Upg B, Upg Lvl B
scr_swordMaster(invSword, "Short Sword", "Long Sword", 3, "Sand Breaker", 5); //Row 0
scr_swordMaster(invSword, "Long Sword", "Pumpkin Eater", 5, noone, noone);
scr_swordMaster(invSword, "Sand Breaker", "Bravado", 3, noone, noone);
scr_swordMaster(invSword, "Pumpkin Eater", "Bravado", 3, "Gladius", 5);
scr_swordMaster(invSword, "Bravado", "Gladius", 3, noone, noone);
scr_swordMaster(invSword, "Gladius", noone, noone, noone, noone);
The code I have in my Draw Event will be identical to my weapon upgrade script, so if I can get it right the first time I just need to replicate it. The Draw code is as follows:

OBJ CONTROL - Draw
GML:
if instance_exists(obj_invGUI) {
    draw_text(30,120,"Selected #: " + string(itemSelected));
    draw_text(30,150,"Grid Ele Name: " + string(inv[# 0, itemSelected]));
    draw_text(30,180,"Grid Ele LVL: " + string(inv[# 6, itemSelected]));
    draw_text(30,210,"Grid Ele UPG A: " + string(invSword[# 1,
                                        inv[# 0, itemSelected]]));
}
** Yes, I'm aware that Royal Sword doesn't have its own row in invSword. I'm more focused on Small and Long Sword items, haha.

Basically, itemSelect is "selecting" the grid row I choose. If I scroll down to a sword, I can obtain all the cells within that row. Obviously, something in the last draw_text line isn't quite working as planned.

My goal for that string is to do this:

Draw: the name of Upg A from invSword by grabbing the name of the itemSelected

In theory, it should function like so:

If (in inv) itemSelect = "Long Sword", upgrade name should be "Pumpkin Eater" (from invSword)
My goal is to simplify this accessor code down so that it can function for any weapon without a million IF statements (weapon list is expected to be ~ 100 items).

Was hoping that someone could see where I'm going wrong. I've even tested just using...

GML:
//For Grid Ele UPG A
...string(invSword[# 1, itemSelected]);
...but it also scrolls in invSword so it's not really pulling data based on the name of the item's weapon.



You can see that after picking up those three swords that were in the middle, the last item "Short Sword" has a Grid Ele UPG A value of -4. This is because in my invSword list, it's pulling from row 5 of that list instead of noticing "hey this item is called Short Sword so let's see what the value 1 of of Short Sword is in this invSword list". I know I could just have it be all in inv but we're talking at least like 10+ arguments - even for items that aren't weapons - so I wanted to differentiate the two inventories.

Maybe I'm wrong on that, tho. Wanted to get some help with this situation before I lose my mind, aha. o_O
 

chamaeleon

Member
If inv[# 0, itemSelected] contains the string "Short Sword", it cannot possibly be used in inv[# 1, inv[#0, itemSelected]] because strings are not numbers to be used as indices into grids, arrays or any other structure that uses numbers for lookup and assignment. I'm not going to attempt to figure out what you should use instead, but whatever it is, it'll have to be a number that makes sense for the purpose to lookup whatever you're storing in inv[# 1, ...].
 
K

KiD_Rager

Guest
If inv[# 0, itemSelected] contains the string "Short Sword", it cannot possibly be used in inv[# 1, inv[#0, itemSelected]] because strings are not numbers to be used as indices into grids, arrays or any other structure that uses numbers for lookup and assignment. I'm not going to attempt to figure out what you should use instead, but whatever it is, it'll have to be a number that makes sense for the purpose to lookup whatever you're storing in inv[# 1, ...].
Thanks for clearing that up!

After revisiting the other functions that came from the tutorial, you're right - each one had constants in the lookup and assignment, then pulled it as a string or number. 😓

I suppose my only option is to just have a pretty wide main inventory and do some IF statements depending on the selected weapon and level. Gonna be messy, I'm afraid.
 

Nidoking

Member
You can map any data type to any data type. Make a map with either numeric or string types on one side and your upgrades on the other.
 
K

KiD_Rager

Guest
You can map any data type to any data type. Make a map with either numeric or string types on one side and your upgrades on the other.
Not sure I follow there.

I'm assuming something like this?


GML:
//Normal swords list
swords = ds_map_create();
swords[? "Small Sword"] = 0;
swords[? "Long Sword"] = 1;
swords[? "Sand Breaker"] = 2;
 
K

KiD_Rager

Guest
If you have some other data structure that uses those numbers as the indices for the swords, that works.
Ah, I see.

Given that, I've modified my code a bit to give weapon IDs for items (0 = Short Sword, 1 = Long Sword, etc etc.). This is without doing ds_maps to see if I can avoid going there at the moment.

Sword master list
GML:
///Master swords list
//Grid, Name, Upg A, Upg Lvl A, Upg B, Upg Lvl B
scr_swordMaster(invSword, 0, "Long Sword", 3, 2, 5); //Row 0
scr_swordMaster(invSword, 1, "Sand Breaker", 5, noone, noone);
scr_swordMaster(invSword, 2, "Pumpkin Eater", 3, noone, noone);
scr_swordMaster(invSword, 3, "Bravado", 3, 5, 5);
scr_swordMaster(invSword, 4, "Gladius", 3, noone, noone);
scr_swordMaster(invSword, 5, noone, noone, noone, noone);

//List
//0 - Short Sword
//1 - Long Sword
//2 - Sand Breaker
//3 - Pumpkin Eater
//4 - Bravado
//5 - Gladius
Testing weapons
GML:
//Test items
scr_itemAdd(inv, "Short Sword", 1, "Basic sword.", spr_wepSword_ShortSword, "sword", wpnEquip, 1, 0, 0);
scr_itemAdd(inv, "Long Sword", 1, "Long sword. Very good reach.", spr_wepSword_ShortSword, "sword", wpnEquip, 1, 532, 1);

As a result, I've modified the inventory code to include that new weapon ID column so my grid size increased by 1. After adding the new DRAW code:

Code:
///Draw
if instance_exists(obj_invGUI) {
    draw_text(30,210,"Grid Ele UPG A: " + string(invSword[# 1, inv[# 8, itemSelected]]));
}
...it almost works.



Now the DRAW string is pulling the weapon ID from the test weapon functions, and THEN using that to find what argument 1 of invSword is. However, it seems that the DRAW is going +1 beyond the itemSelected value. The process should be:

Short Sword -> Long Sword
Long Sword -> Sand Breaker


...but instead it's skipping and doing this:

Short Sword -> Sand Breaker
Long Sword -> Pumpkin Eater


If I go into my test weapon functions and reduce each last argument (weapon ID) by 1, it works but breaks Short Sword (because you can't have a -1 lookup value).

Now I'm stuck as to what's even going on, haha.
 

Nidoking

Member
Why are you using strings and not weapon IDs for the upgrade value? The point of using an ID is so you have a single, common way to refer to a thing everywhere. Once you have numeric IDs, you don't need the strings aside from that single table that lists all of the attributes of each weapon by ID.
 
K

KiD_Rager

Guest
Why are you using strings and not weapon IDs for the upgrade value? The point of using an ID is so you have a single, common way to refer to a thing everywhere. Once you have numeric IDs, you don't need the strings aside from that single table that lists all of the attributes of each weapon by ID.
My thought process was to rely on strings because I'll be throwing names around a lot. If I told the game to fetch inv[# 0, itemSelected], that equates to cell(0,0) which holds a name like "Short Sword". Plus prior to most of the comments here, I was under the assumption that lookups for strings were possible. I guess I was wrong in that, haha.

Because if I have an invSword like so:

GML:
//Grid, Name, Upg A, Upg Lvl A, Upg B, Upg Lvl B
scr_swordMaster(invSword, 0, 1, 3, 2, 5); //Row 0
scr_swordMaster(invSword, 1, 2, 5, noone, noone);
scr_swordMaster(invSword, 2, 3, 3, noone, noone);
scr_swordMaster(invSword, 3, 4, 3, 5, 5);
scr_swordMaster(invSword, 4, 5, 3, noone, noone);
scr_swordMaster(invSword, 5, noone, noone, noone, noone);
...where would the names be fetched from? After the game pulls data from this it'll only display those numerical values that don't mean anything. I'm trying to also think of your suggestion of doing ds_maps but I can't figure out how it'd work with this.

EDIT: I realize I didn't give a background as to why I'm trying to do this, my bad.

Basically I'm planning to work the weapon system as such:
  • Weapon tree similar to that of a multi-branching skill tree (where a weapon can have many ins/outs)
  • Duplicates of the same weapon
  • Player can build up a weapon with several stats (attack, elemental attributes, etc.) that differ compared to the base version of that weapon
So if I have a Short Sword and level it up, it'll eventually have improved stats across the board. If I pick up a Short Sword along the way, however, the newly picked up item will have lower base stats that the player's sword originally had.

If that makes sense.
 
Last edited by a moderator:

Nidoking

Member
You can use strings as indices IF the data structure is a map.
You can use numbers as indices if the data structure is a map, an array, a list, a grid, etc.
You asked how you could use the string to look up a piece of information. I told you. You use the string as the key to a map where the value is the information you want.
Then you introduced numbers. You can use the numbers as indices into arrays, lists, or grids. You can also use the numbers as keys into a map, which works better if they're large and nonconsecutive. For small or consecutive numbers, use an array, list, or grid.
The names are fetched from where you put the names. I have no idea where you've put the names at this point. But if you have IDs for the weapons, you want to use the weapon ID to find the name, not the other way around. Make a big grid, or some maps, or some arrays, and set all the information for each sword in that structure. When you need to store a reference to a sword, store the ID. When you have an ID and need to retrieve information, use the ID to look up the information.
Maybe you should just draw a diagram of how all the information should be laid out, without worrying about code or data structures. Then you can figure out how you want to do it. Trying to design after you've written it is backwards.
 

Japster

Member
@Nidoking is spot on - use numbers, and a look-up grid to store the names (and maybe another to store materials, unique item prefix names, etc if they're involved).

I'm not sure if I'm helping or not, and I ramble, so you might want to grab a coffee! - but to make it easier to work with grids, arrays etc, for data access in my games, I've used 'enums' before - they helped a huge amount! (and apparently structs are appearing in the latest GMS2?)

So, you could basically have:-

GML:
enum Weapon_Type {
   Short_Sword,
   Long_Sword,
   Mace,
   Dagger,
   <etc...>
}
...Which would default to Short_Sword = 0, Long_Sword = 1, etc

...or set your own values (I don't tend to do this personally, as I like all fields filled and ordered as I set up the enum)...

You can set up enums for any grids you might use, and you can also refer to these in switch / case statements of course:-

GML:
enum Weapon_Type {
   Short_Sword = 1,
   Long_Sword = 2,
   Mace = 5,
   Dagger = 17,
   <etc...>
   }

enum Equipped_Weapon {
   Weapon_Type,
   Level,
   Material,
   Prefix,
   Element_Type,
   <etc...>
   }

enum Material {
   Steel,
   Iron,
   Wooden,
   <etc...>
   }

enum Weapon_Stat {
   Attack,
   Elemental_Damage_Type,
   Elemental_Base_Dama,
   Cooldown,
   Bleed_Damage,
   <etc...>
   }

enum Element {
   Fire = 1,
   Earth = 2,
   Water = 3,
   Wind = 4,
   <etc...>
   }

Then you could create your data grids, and refer to each weapon type in code, by

ie. if / switch /case
tmp_Stat = Weapon_Types_Grid [# Weapon_Type.Dagger, <Stat_You_Want>]
eg:- Weapon_Types_DB [# Weapon_Type.Dagger, Level, Weapon.Element_Type] could return which elemental damage (if any) is set, at the requested level....

Using enums also means you shouldn't have any issues with 'magic numbers' if you re-work stuff - you simply modify the enum order / value assignments at the declaration, but don't have to change any references all through your code?

For descriptions, you can simply have a grid or array of strings for names (and possibly one for materials or special names/prefixes, like "Skullcrusher", "Platinum" etc), and just grab those elements by using "Materials.<My_Material>" + "Item_Prefix.<My_Item_Prefix_ID>" + "Weapon_Type.<My_Weapon_Type>" could return "Obsidian Skullcrusher Mace" for example? (This also makes maintenance a LOT easier, if you're changing descriptions during dev, etc - change it in one place, done.)

I'm assuming you've got a grid with stats for levelling up, or is it calculated? - again, I'd expect you probably have stored values for levelling up effects or calculations, especially if you've got set 'perks' per level, as opposed to calculated improvements to stats?

ie. Simply Weapon Type, Per Level DMG multiplier, or alternatively a set value or multiplier, per level column, etc? I'd imagine you can (or are) also storing other info, like effectiveness against X, critical chance, enchantments, etc (but I digress!)

So, even when referring to this grid, you can use the enum to select the right row/column...

ie. Checking (Weapon_Type.Short_Sword, 10, Weapon_Stat.Attack) could return the level 10 ATTACK base damage multiplier, etc....
or...


Code:
Equipped_Weapon_Type = Player_Weapons [# Equipped_Weapon.Weapon_Type];
Equipped_Weapon_Level = Player_Weapons [# Equipped_Weapon.Level];
Attack_Base_Damage = Weapon_Types [# Equipped_Weapon_Type, Equipped_Weapon_Level, Weapon_Stat.Base_Attack];
I've also used macros - they work well, and you won't need to prefix them, but basically, as others have mentioned, referring to and cross-referencing stuff based on strings sounds a hard (and potentially painful) way of doing this... :)

Internally, it won't matter what actual number is being used for reference, if you can help make it comprehensible by just using enums or macros, then use those to grab the string in question for displaying. Again, I'm relatively new to GMS2, and bring some old, bad habits with me, but seriously, I learnt very quickly that it made sense to use enums to allow me to use something readable and maintainable / easier to work in, instead of magic numbers, and never looked back. They're also instant compared to looking up matching values, as they're directly relatable to field / cell numbers...

Sorry, looooong (and late!) post, I type as I think! - if any of that makes sense, then great! - if not, and I've completely lost the plot, let me know!... :D

I might load up GMS2 later on today (3am here now) and try to make a better example for you... :)
 
Last edited:

Japster

Member
Actually, after all that, this guy (User : Simon Gust) explains it really well here, for a VERY similar example to what you're trying to achieve:-

enums info!
 
K

KiD_Rager

Guest
You can use strings as indices IF the data structure is a map.
You can use numbers as indices if the data structure is a map, an array, a list, a grid, etc.
You asked how you could use the string to look up a piece of information. I told you. You use the string as the key to a map where the value is the information you want.
Then you introduced numbers. You can use the numbers as indices into arrays, lists, or grids. You can also use the numbers as keys into a map, which works better if they're large and nonconsecutive. For small or consecutive numbers, use an array, list, or grid.
The names are fetched from where you put the names. I have no idea where you've put the names at this point. But if you have IDs for the weapons, you want to use the weapon ID to find the name, not the other way around. Make a big grid, or some maps, or some arrays, and set all the information for each sword in that structure. When you need to store a reference to a sword, store the ID. When you have an ID and need to retrieve information, use the ID to look up the information.
Maybe you should just draw a diagram of how all the information should be laid out, without worrying about code or data structures. Then you can figure out how you want to do it. Trying to design after you've written it is backwards.
Right, I understand that. I think you're right about the maps then after all - for some reason I had a massive brain fart and combined numerical and string keys and got myself confused. The ID thing will make more sense to finding the name. Thanks for explaining that further for me, haha.

@Japster I thought about enums right after I added the inventory actually! Because I'll have a TON of variables (10 alone for elemental attributes, dozens of weapons just for 1/6 of the weapon types we're planning, etc etc), I figured enums would appear in my future but wanted to see if I'd get away with doing it just with data structures alone. Now is looking like I'll need to incorporate it all. 😓 Also, I dig the idea of a grid/array for names like the example you put. Seems very clean, just means more things to call for (but I figured as much).

Thank you both for the awesome ideas! I'll have to re-do some of my invSwords code as a result and a few other functions elsewhere, but hopefully integrating these makes this less painful haha. :)
 
K

KiD_Rager

Guest
You guys are absolute units.



I made one of the inv[] arguments "newItemWpID" that holds the numerical ID of the sword. With the help of enum, I converted each weapon into a number (Short Sword = 0, Long Sword = 1, etc). Then I expanded my invSword[] grid and used the example that @Japster linked me to. After that, I modified the DRAW so that it pulled the weapon ID number of the item I have selected, paired with another enum for the stat I want to grab, and it works!

invSword code
GML:
///Initialize
globalvar invSword, invSwordWidth;
invSwordWidth = 6;
invSword = ds_grid_create(invSwordWidth, 6);
/*scr_listSwords();*/

enum weapons {
    short_sword = 0,
    long_sword = 1,
    gladius = 2
}

enum stats {
    name = 0,
    desc = 1,
    lvl = 2,
    upg_lvl = 3,
    upg_a = 4,
    upg_b = 5
}

invSword[# weapons.short_sword, stats.name] = "Short Sword";
invSword[# weapons.short_sword, stats.desc] = "Small sword. Easy to weild.";
invSword[# weapons.short_sword, stats.lvl] = 1;
invSword[# weapons.short_sword, stats.upg_lvl] = 3;
invSword[# weapons.short_sword, stats.upg_a] = "Long Sword";
invSword[# weapons.short_sword, stats.upg_b] = "Gladius";

invSword[# weapons.long_sword, stats.name] = "Long Sword";
invSword[# weapons.long_sword, stats.desc] = "Large sword. So-so attack, decent reach.";
invSword[# weapons.long_sword, stats.lvl] = 1;
invSword[# weapons.long_sword, stats.upg_lvl] = 5;
invSword[# weapons.long_sword, stats.upg_a] = "Gladius";
invSword[# weapons.long_sword, stats.upg_b] = noone;
DRAW code
GML:
draw_text(30,210,"Grid Ele UPG A: " +
    string(invSword[# inv[# 8, itemSelected], stats.upg_a]));
@Nidoking was right on the money about a, ID for each weapon in question. I understand that now it's easier to just reference this enum grid and then have each item ID belong to whatever enum and reference there. Now I can "happily" move onto the actual upgrading process. 😂

Cheers mates!
 

Japster

Member
Excellent! - Wow! - you don't waste time! :)

...glad you got it sorted, and glad it all made some sense! - it's always a great feeling knowing that we've made a difference and been able to help someone out! ;)

Nice to get a heartfelt thank you on here too - I think I speak for both myself and @Nidoking when I say, "Cheers!"... :)

Have fun coding (and more importantly, winning at it!) :D
 

Nidoking

Member
I think I speak for both myself and @Nidoking when I say, "Cheers!"
Quite so. I didn't want to muddy the waters by pointing out that this is the sort of thing that can be done in more intuitive ways in other languages (and I think some of the features coming up in GML 2.3 might offer other ways as well), but this is my Game Maker 2.2 solution.
 
Top