Back to Main Page


This is a page about using Collections to manage your entities.

This isn't an in-depth overview of each type, it's more a typical-use kind of introduction, that explains the basics and shows how and where they can be used to add functionality to your mods.

 

Entity Collections - Arrays, Lists and Dictionaries


At some point, you're going to want to deal with more than just a single entity at a time, whether that be a collection of Peds about to attack the player, or a collection of Vehicles stored in your garage, or even a collection of Props for your map. When this time comes, you have a few options to choose from.

At the most basic level, you could use an array:

Ped[] MyPeds = new Ped[5];

And that will create an array that will hold 5 peds. That might be fine for most cases but arrays aren't that friendly if you want to do more. If you want 6 peds for instance, then you're going to have to create a bigger array and then move the original array into the bigger array. It's a bit like having a box for 6 eggs and suddenly needing to put 8 eggs in the box.

Arrays are very functional when it comes to holding data, like a string[] to hold the lines from a textfile, or a byte[] to hold binary data, even a string itself is nothing more than a char[], so they are useful in the right context. The other limitation is that if you no longer wanted the Ped at position 4, the only way to remove it would be to move everything above it down by 1 and then set the end element to null. It's not ideal but at one time, it was all we had.

So the next step up to something more manageable, is the List<>. A List is a collection with an unspecified size, which means you never run out of space. But you also never use more space than your collection contains. So unlike the array, you would simply have this:

List<Ped> MyPeds = new List<Ped>();

That creates an empty collection of Peds and all you need to do to add to that collection, is:

MyPeds.Add(MyNewPed);

If you want to understand the simple structure of a List<>, think of a stack of paper. Things are added to the List<> in a top-down fashion, which means the newest item is always at position 0. The benefits of a list become clear when you want to manage that collection. If we have our collection of 6 Peds and we no longer want the one at position 4, then all we need to do, is:

MyPeds.RemoveAt(4);

Now, the Ped is removed and the list automatically closes the gap for you. As well as RemoveAt(), you can remove an item directly using Remove(), or you can remove several at once with RemoveRange(). Those functions are matched when you want to add or insert items with AddRange(), Insert() and InsertRange(), so you can see you have a lot of control over your collection.

The last collection type I will cover is the Dictionary<Key, Value>(). This is an extremely powerful collection type, that allows you to organise your entities into more manageable sections, or identifiable collections.

A Dictionary is made up of two parts, the Key and the Value. The Key is typically a single data-type that you use like the keywords in a language dictionary. So you might use a string, or an int for example. The Value can be anything and this is where the real power of a dictionary comes into play.

You could have a simple Dictionary<string, Vehicle> where you store all the vehicles based on their Vehicle.NumberPlateText for instance. You could have a Dictionary<int, Ped> where you store Peds based on their Ped.Handle property. But you could also have a Dictionary<string, List<Vehicle>>, where you can store collections of vehicles based on their Brand name. You can even have a dictionary as the value of a dictionary. The only limit to a dictionary's complexity, is your confidence in dealing with complex collections.

So you create your dictionary like this:

Dictionary<int, Ped> MyPeds = new Dictionary<int, Ped();

And you can add an item like this:

MyPeds.Add(1234, MyNewPed);

So in that instance, 1234 is the Key and MyNewPed is the Ped that belongs to that key, the Value. So what are the rules of a dictionary, well it's fairly simple, every Key has to be unique. So having done the above, trying to do this would be invalid:

MyPeds.Add(1234, MyOtherNewPed);

This is because the Key 1234 is already used. To check if a key is used, you can use the Dictionary.ContainsKey() check first and then make a choice based on the result of that check.

I must stress one important difference between Lists and Dictionaries at this point and that is a dictionary has no index value. Let me explain that... imagine you had this:

Dictionary<int, Ped> MyPeds = new Dictionary<int, Ped();

MyPeds.Add(1234, MyNewPed);

Adter doing that, you can't do:

Ped myPed = MyPeds[0];

Dictionaries are not stored as a linear collection, that you can iterate through in a numeric sequence... more on this later.**

So as far as managing dictionaries go, you can Add() something and you can Remove() something and you can set a value to something. So to just go back to the invalid line above, what you could do in that instance, is this:

MyPeds[1234] = MyOtherNewPed;

That would change the Value associated with the Key 1234. Now you might have already guessed at this, but when you are processing collections of entities, the dictionary is absolutely perfect. Why you might ask? Well every Ped.Handle or Vehicle.Handle is unique. So if you did this:

MyPeds.Add(MyPed.Handle, MyPed);

You're guaranteed to have a unique key and to check if the Ped you have just collected is already in your collection, all you need to do is:

if (MyPeds.ContainsKey(Ped.Handle))
{
}

Which makes it easy and fast to eliminate entities you have already dealt with.

**Collection Gotchas

By now you're possibly convinced about how great Lists and Dictionaries are but beware, they come with gotchas that can very easily catch you out. A common way to iterate through a collection is with a foreach loop and that can be used on both a List and a Dictionary in a slightly different way.

List<Ped> MyPeds = new List<Ped>();

foreach (Ped p in MyPeds)
{
}

That's how you would go through each Ped in a List. For dictionaries, you typically have two options:

Dictionary<int, Ped> MyPeds = new Dictionary<int, Ped>();

foreach (KeyValuePair<int, Ped> kvp in MyPeds)
{
    Ped p = kvp.Value;
}

Or...

foreach (Ped p in MyPeds.Values)
{
}

Both of those will let you deal with each Ped in the collection. But here's the gotcha, while you can go through each Ped like that, you can't change it, or delete it. You can't modify a collection while you are iterating through it. If you try and modify the collection, you will get this error:

Collection was modified; enumeration operation may not execute.

A foreach loop expects the collection to remain intact while it goes through each item in the collection. That applies to both the List and the Dictionary. So how do you solve that problem? Well with a List it's fairly simple:

for (int i = MyPeds.Count - 1; i >= 0; i--)
{
}

Using an index on a list in reverse, allows you to delete the item at that index. The reason being that the next one to be processed, would be behind the one you have deleted, not in front of the one you are deleting. For dictionaries, because you don't have the index, the simplest way is to do this:

foreach (int i in MyPeds.Keys.ToList())
{
    if (MyPeds.ContainsKey(i))
    {
        Ped p = MyPeds[i];
    }
}

This way, you are accessing the dictionary by key from a copy of that collection, so if you remove an item, it doesn't matter because the List of keys are the ones being iterated through and that will always be a complete collection, no matter what happens in the dictionary.

Practical Scenario

So let's look at a practical use of these collections in a scenario I was asked about. This solution uses substitute tasks rather than ones that I was specifically asked about.

Imagine you want to perform a task on vehicles, actually, two tasks. Task 1: Open the driver's door, Task 2: Turn the Lights on. But you only want each task to apply to certain vehicles. Task 1 is for the player's current vehicle, Task 2 is for all the vehicles you have used in the current game session.

For the current vehicle, we can simply use a single Vehicle property.

private Vehicle CurrentVehicle;

For the collection of Vehicles, we can use either a List<Vehicle> or a Dictionary<int, Vehicle>. I will demonstrate both side by side:

private List<Vehicle> PreviousVehicleList = new List<Vehicle>();
private Dictionary<intVehicle> PreviousVehicleDict = new Dictionary<intVehicle>();

Each time we get into a new vehicle, CurrentVehicle gets set to the Game.Player.Character.CurrentVehicle. But before setting CurrentVehicle, we need to make sure that if it's not the same as the one we have just got into, then it might need to go into the collection.

So for the List:

if (!PreviousVehicleList.Contains(CurrentVehicle))
{
    PreviousVehicleList.Add(CurrentVehicle);
}

and for the Dictionary:

if (!PreviousVehicleDict.ContainsKey(CurrentVehicle.Handle))
{
    PreviousVehicleDict.Add(CurrentVehicle.Handle, CurrentVehicle);
}

Then when you are near the vehicle and you press the specified control, you compare to see if it matches CurrentVehicle and if so, do Task 1, or if not, see if it's in the collection and if so, do Task 2.

// if we are on foot and press the Jump control
if (Game.IsControlJustPressed(2, Control.Jump) && PlayerPed.IsOnFoot)
{
    Vehicle nearbyVehicle = World.GetClosestVehicle(PlayerPed.Position, 5f);

    // If there is a nearby vehicle
    if (nearbyVehicle != null)
    {
        // If the nearby vehicle matches the CurrentVehicle, do Task 1
        if (nearbyVehicle == CurrentVehicle)
        {
            DoTask1(nearbyVehicle);
        }
        else
        {
            // If the vehicle exists in either the list or the dictionary, do Task 2
            if (PreviousVehicleList.Contains(nearbyVehicle) ||
PreviousVehicleDict.ContainsKey(nearbyVehicle.Handle))
            {
                DoTask2(nearbyVehicle);
            }
        }
    }
}

You can download a complete commented script here. In this sample, when you press Jump near a vehicle that matches the current vehicle, the door is opened. When you press Jump next to a previously used vehicle, the lights are turned on. Each task has its own function, so they can be replaced with whatever task you want to happen. To remove the vehicles from the collection, use the methods shown earlier for the List and the Dictionary.

Addendum: It occurs to me that the examples I have used in the main section might not have been the best examples in hindsight. Storing Peds or Vehicles with their Handle as an Index serves no real benefit, as the Peds or Vehicles themselves are already unique. A better example for a Dictionary would probably have been a collection of Menu items, ParticleFX data or animation lists.

ParticleFX have two components, the library, like "scr_carsteal4" and the effect, like "scr_carsteal4_wheel_burnout". So if you create a Dictionary<string, List<dynamic>>(), the Library name can be the Key and the List<dynamic> (the Value), can contain the names of all the effects in that Library. So if you have a menu, one menu item can be a list of library names and as you change that item, the effect menu item is populated with the list of effect names. Note: I could have used a List<string> but NativeUI uses List<dynamic> for UIMenuListItems, which is why I chose that option.