#Best practice for storing item data with functions

1 messages · Page 1 of 1 (latest)

dense jungle
#

I am brainstorming an easy and flexible way to store item stats in my game.
I would like to do something preferably, as easy as item.<item>.function() in my code so that coding new items (and adding more through mods) is as easy as setting a few parameters, and then writing a basic function to run every frame
However, due to this, I really don't want to type out
items = {
item1 = {
name = "example",
model = "model",
function = function()
{ .... }
},
etc. etc.
Is there an easier or more concise way to do this? EVERY item needs the exact same parameters so typing it out manually every time would get extremely tedious.
Or even a DS Grid to store items instead? But how would I access the functions, then?

Due to wanting mod-ability to be a big part of the game, storing functions inside of the item data is a big deal, I don't just want pointers to a bunch of functions I write elsewhere, unless that's the best / my only option.

golden ivy
#

constructors will doe xactly this for you!

#

You would have a constructor

#

like so

#
function Item(_name, _model,_function) Constructor{
  name = _name;
  model = _model;
  function = _function
}
#

and then when you want to make a new item

#

you call

#
item = new Item("example","f 150", function(){do_something})
dense jungle
#

Thank you! I learned about constructors like a few years ago and I swear I knew there was an easier way I just couldn't quite remember it.
Since I have your attention, and I saw you make Isaac videos and rougelikes, I was wondering, are you familiar with a solution to replicating a system similar to callbacks in Isaac modding?
My current solution is just a ds_map of variables which get set to false every tick (after every item's code has ran) and then I set these variables to true or whatever inside of the events.
This is so that I can do if player_just_got_hurt basically or whatever and run parts of the code when that happens (for ease of programming conditional items, especially for modders who can't see the code and will only have my list of callbacks and functions) but I was wondering if there was a cleaner way to do this than my method? This feels like something that probably has a more intended solution

golden ivy
#

you can make the player have a function damage() which loops over every collected item and calls their player_just_got_hurt function if it exists. ```gml
damage = function(_amount){
hp -= _amount;
var number_of_items = array_length(items)
for(var i = 0; i<number_of_items;i++){
var current_item_method = items[i][$ "player_just_got_hurt"]
if(is_undefined(current_item_method))continue;
if(!is_method(current_item_method))continue;
current_item_method(_relevant_variables_youre_passing_to_this_function)
}
}

#

other ways to do it as well

#

so when you damage the player you call that

#

and then it calls all the items

dense jungle
#

Interesting! And how would I go about giving an item a "player_just_got_hurt" function?

#

Would I need to add that to my constructor or could I put it inside of the _function variable when constructing the item?

golden ivy
#

you would pass it in, or you add an "addOnDamage" function to the constructor

#

then you cal construct the item

#

and then do

#

item.addOnDamage(_onDamageFunction(){})

#

you will get trapped in scope hell

#

to make sure the functions are called from the correct positions

dense jungle
golden ivy
#

whoa there

#

I never said anything about best

#

this is a solution

dense jungle
#

That's true.

golden ivy
#

you can also do item.addCallback(_function,_key){}

#

and then bind the callback to the key

#

that way you dont need to write a function for adding every callback

#

so if you wanted a damage function

#

you'd do

#

item.addCallback(_function,"player_just_got_hurt")

#

and then in there bind the function to the variable "player_just_got_hurt"

#

so that would be

#

actually best to swap those arguments

static addCallback = function(_key,_callback){
  self[$ _key] = method(self,_callback); //the self here is whatever scope you want the callback to have, could be obj_player as well
}
dense jungle
#

Oh! The gears are turning. I think I'm understanding.
With that in mind, I don't actually think I need a function for every item then.
So if I had an item that had a function for when you first obtained it, every frame you have it, and then whenever you get hit, I could do:

item = new Item("example","f 150", "whatever")
item.addCallback(_function( do something), "player_just_got_hurt")
item.addCallback(_function( do something), "just_got_item")
item.addCallback(_function( do something), "every_frame")
#

Am I understanding this right?

golden ivy
#

yep

#

and then you just need to call those when appropriate

#

you can even sort them into buckets when you pick them up

#

so when you pick up an item with on_damage you put it into your items_with_on_damage_effect array

#

that way you don't need to loop over every item when you take damage

#

only the ones that will actually do something

dense jungle
#

That's true!

golden ivy
#

I will say, for something like every frame you can consider just spawning an object that does something

#

but you can loop over the every step as well

#

it might get a little costly that way but who knows

dense jungle
#

One more question, how would you suggest I store a list of every item in the game? item = new Item is fine but there's not really a good way to iterate through that if I need to say, pick a random item to give a player

golden ivy
#

you need a library of every possible item somewhere

#

could be a struct, an array, whatever you prefer

#

also

#

I recommend not taking the items directly from the library

#

but to make copies of the structs inside the library

#

if the player somehow gets the same item twice you don

#

't want it to be the exact same item twice

#

here you'd use

#

or, even better

dense jungle
#

That's what I had planned on doing, especially because my game has a unique gimmick where item tiers go up the more you get them (combo'd with a system where your max tier goes up as you choose to give yourself harder enemies) so items will have stats (well, a stat, but still) that change over the course of a run, so duplicating the list was already something I had planned

golden ivy
#

make a static generate function on the item

#

which returns a variable_clone

#

but you can have all the other code needed in there

#

like for example if you have a list of every item in an item pool

#

and you want to ensure that you don't generate an item the player already has

#

you can edit that generate() function to not only return a copy, but remove it from the relevant pools

#

this means you actually need 2 libraries of every item you use to populate a run, and then one inside the run to see what you can actually get at the moment

dense jungle
#

Okay, I'm sorry, I'm a bit lost, I don't think I quite understand why I need to duplicate the list just yet. Can't I just store a list of numbers which correspond to item IDs, and then simply remove the item ID from that list, instead of needing to store a copy of the entire item?

golden ivy
#

sure works too

#

just any sort of reference that clearly identifies it

dense jungle
#

Okay, so, would it be okay to:

  • Make an array containing every item constructor, so that to get information on an item I just do item[23].model or something (I don't know the accessor off the top of my head)

  • At the start of a run, make a (temporary) different item pool for every tier (It would be the simplest for what I have planned) and iterate through the item array placing every item ID in if its the proper tier

  • As the run goes and item ranks change, simply move the items around in the different pools as needed

  • Create a ds_list of every item ID the player currently has, then iterate through that list whenever I need to call functions for items (or make multiple buckets instead, same principle)

  • Then of course delete it when all is said and done

#

Is there any problems that might arise from this? I'm only ever reading from, in my example, the item[] array and never writing to it, only grabbing what I need to and storing them in lists.

golden ivy
#

for arrays using the . like that is fine

#

for the struct accessor using the [$ ] is important, as undefined will crash the game if you try to get it with a .

dense jungle
#

Gotcha.

#

So what format would I use to access the model variable of item[x]

golden ivy
#

well every item is going to have a model

#

so you're fine

#

the constructor ensures that

#

but for the callbacks, not every item has an on_damage

dense jungle
#

But when it comes to callbacks, which is not guarunteed, use the $ accessor

golden ivy
#

so there you need to do item[$ "on damage"]

#

exactly

dense jungle
#

Wait, but what about the item's ID? I don't do, say, item[25][$ "on damage"], do I? Or is item in your example item[25] or do I not need to iterate through the array and the array handles it for me

golden ivy
#

thats exactly what you'd do

#

in my example

dense jungle
#

Gotcha, arrays are cool!

golden ivy
#

var current_item_method = items[i][$ "player_just_got_hurt"]

dense jungle
#

Gotcha.

#

You've been very helpful, thank you so much!

golden ivy
#

any time 🫡

#

except for when im busy or don't feel like helping

dense jungle
# dense jungle Oh! The gears are turning. I think I'm understanding. With that in mind, I don't...

I have one last question, actually.
Ideally, I construct my array like

items = [ new Item("whatever"), new Item("whatever2")]

But then, when I want to add callbacks, where exactly do I do it? Do I have to add in every callback after I add the item to the array, since I can't just squeeze items[2].addCallback into the array itself, but I don't want to seperate adding an item to the array with adding its callbacks, so how exactly do I go about this?

golden ivy
#

I would create the array like that

dense jungle
#

What if I need to change the order of items? It's unlikely, but I don't want to have to remember item ids when adding callbacks, especially since I plan on adding modding support and it would be impossible for someone to know where they are in the array without being able to see it, no?

#

...I guess I could just, make a function to add an item to the array and then make add the callbacks from there, that would work, right?

golden ivy
#

You can create all the items, add the callbacks, then feed them into the array

dense jungle
#

Oh my gosh you're right

golden ivy
#

You can also write a function that searches through the array by element

#

And checks if their name matches the argument

#

findItemByName function(itemname)

dense jungle
#

That's true. Though, I think that adding them all at the end would work better because it would allow mods to create items with the same name without worrying about adding functions to the wrong items

golden ivy
#

Which loops over item and returns the index of the array of the contents matched the name

#

You can have mod loaded items have a mod prefix

#

But whatever

#

The callback stuff is gonna be harder for modding

#

Have you looked into #katsaii___catspeak

dense jungle
#

Well, I'd rather not give items a "display_name" and then a "internal_name" because, y'know, why would we need an internal name? We got an array position right there to identify unique items

#

I have not!

#

It looks very very handy, however.

golden ivy
#

There are tons of reasons for that

#

But I am going to bed

dense jungle
#

Alrighty, goodnight friend and thank you very much for the help.

golden ivy
#

🫡

snow epoch
snow epoch
#

I think if you want to use indexing like that you'll probably want to have it assign indexes and give them back to the mod code on register but that's just my pov

#

you could also have the items be represented in data instead of code and have code that loads the data, which tends to make modding a lot easier

dense jungle
#

I ended up giving them unique identifiers and prefixes as well

#

Mostly because of what you said but also because it saved time in my lang file

thick plinth
#

tip for localisation - don't put numbers into the loc file if you can help it

#

makes life much harder if you need to change those numbers later

snow epoch
#

you can have the numbers auto insert in various ways, although you'll need to be careful not to have it crash if there isn't a number for it to insert

dense jungle
#

Good tip. Thank you! I also need to insert the various stats so building descriptions from different parts is something I needed to do anyways