How to reconstruct an array of inventory items in a certain order?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Diet Estus

I am trying to implement a save feature for my inventory system.

My inventory is a simple array in which I store nodes corresponding to my items. For example,

var inventory = [get_node("Item1"), get_node("Item2"), get_node("Item3")]

Item nodes have a save_dict() method which returns a dictionary of their persistent attributes (in this example, just count).

When I save my game, I iterate over these nodes:

func save_game():
    var inventory_data = {}    
    for item in inventory:
        inventory_data[item.file_path] = item.save_dict()
  # save inventory data as JSON
  ...

The overall structure of inventory_data is

{
"res://Item1.tscn":{"count": value},
"res://Item2.tscn:{"count":value},   
"res://Item3.tscn:{"count":value}
}

I save this dictionary as a JSON file and when I load my game, I use this dictionary to reconstruct my inventory.

func load_game():
    # load inventory data from JSON
   ...
    for file_path in inventory_data.keys():
        var item = load(file_path).instance()
        for attribute in inventory_data[file_path]:
            item.set(attribute, inventory_data[file_path][attribute]
    inventory.append(item)

The problem is that when my player collects items, they are added to inventory in a certain order. When he pulls up the inventory menu they are displayed in that order.

But when he saves the game, the items are iterated over in the correct order, but they are added into the inventory_data dictionary in a different order. (I suspect the dictionary automatically sorts the keys alphabetically.)

Thus, when the game is loaded, the items in inventory are in a different order than they were before the save. They correspond to the order of the dictionary.

My question: How can I save my inventory data in such a way as to keep the order of inventory?

:bust_in_silhouette: Reply From: coffeeDragon

First you need to save the index of the item.
So using an range iterator while saving, instead of the standard for each. for i in range(inventory.size())
Then saving it as an array alla "res://Item2.tscn:{"count":[index, value].,
And while loading: initzialize the array, with the right size, use inventory.resize(inventory_data.size()) and then insert them at the right place.
inventory.insert(index, val) or inventory[index] = val

:bust_in_silhouette: Reply From: funabab

I think this issue as been addressed in 3.x but a temporary solution is to also save the index with the data

func save_game():
    var inventory_data = {}    
    var index = 0
    for item in inventory:
        inventory_data[item.file_path] = item.save_dict()
        inventory_data[item.file_path].index = index
        index += 1
        # save inventory data as JSON
        ...

and sort it with the index before using it. You can create a dictionary sorter as an inner class

class DictionarySorter:
    var dict
    func _init(dict):
        self.dict = dict

    func sort():
        var result = {}
        var keys = dict.keys()
        keys.sort_custom(self, "_sort_dict_keys")
        for key in keys:
            dict[key].erase("index")
            result[key] = dict[key]
        return result

   static  func _sort_dict_keys(key_a, key_b):
        return dict[key_a].index < dict[key_b].index

just before you loop through your inventory_data, you call

inventory_data = DictionarySorter.new(inventory_data).sort()


it’s​ not necessarily you use an inner class to sort the dictionary, the same thing can be done using a single function but i think this is easier to understand IMO
BTW code was not tested