Getting Started with EntityData

From GTA Network Wiki
Jump to: navigation, search

Getting Started with EntityData

In this page you will learn everything you need to know about the EntityData functions that are avaliable for you to use. The list of functions you can use that are related to EntityData are the following:

There's also an event that gets called whenever the value of an entity data key is modified:

What can I do with this functions?

This set of functions allow you to store any type of custom data you want on any type of entity, and share this custom data with all the clients on the server if needed. For example, you could store the amount of fuel a vehicle has using the SetEntitySyncedData function and then access this data on the client-side to create a heads-up display that shows the fuel of the vehicle the player is currently in.

Difference between Synced EntityData and normal EntityData

You probably noticed that the are two different groups of functions related to EntityData: Synced EntityData and normal EntityData.

The only difference between the two groups of functions is that the data set with the Synced EntityData functions are indeed synced with the clients. All the data you save on an entity using the synced EntityData functions will be shared with all the clients on the server, and you as a developer will be able to access this data on the client-side scripts using the same functions:

The data you set using the normal EntityData functions (non-synced) is only stored on the server. This data will not be shared with the clients and they will not be able to access it.

When should I use the synced EntityData functions over the normal ones?

You must be very careful when using the synced EntityData functions. In order to be able to share the data with the clients you must enable the trust_client_entity_properties option in your server's settings file (Settings.xml) - In order to do this you simply must set the option to true.

By doing this not only are you allowing the server to share the synced EntityData with the clients, but you are also allowing them to modify this data on the client-side by using the SetEntitySyncedData function, so you might not want to store sensitive data on synced EntityData because theoretically a player with a modified client could possibly abuse this functionality to edit data they should not be allowed to edit. So you should only sync EntityData when absolutely needed.

Following is an example involving both the use of synced EntityData and non-synced EntityData, and how to implement server-side security for synced EntityData.

Example of EntityData usage

Suppose you want to implement a player levelling system in your gamemode and you want to show the player's level on their screen at all times. We will need to store the level information of the player somewhere so you can access it on the client-side using Javascript. For this example we will need to use synced EntityData.

When a new player connects we will set a new synced entity data key and store the player's level on it. We will use the OnPlayerFinishedDownload event to achieve this.

public YourConstructor()
{
     API.OnPlayerFinishedDownload += OnPlayerConnect;
}

private void OnPlayerConnect(Client player)
{
     API.SetEntitySyncedData(player.handle, "LEVEL", 0);
}

This will create a new entity data key with the name "LEVEL" for the players that connect to the server and store the number 0 in it (32-bit integer). Now we will need a function to get and set a player's level, so we will write this two simple functions for that.

public void setPlayerLevel(Client player, int level)
{
     API.SetEntitySyncedData(player.handle, "LEVEL", level);
}

public int getPlayerLevel(Client player)
{
     // Always check if the data exists before accessing it
     if(API.HasEntitySyncedData(player.handle, "LEVEL"))
     {
          return API.GetEntitySyncedData(player.handle, "LEVEL");
     }
     
     // Just return 0 if it doesn't have the data
     return 0;
}

So you might be wondering why we created two functions that just call other functions. Why not just call SetEntitySyncedData or GetEntitySyncedData directly? Don't worry, we will add some more stuff to those functions later, so we will keep them that way for now.

Now for the fancy stuff. We will add a text to the hud that will display the player's level on the screen. To achieve this we will use the OnUpdate client-side event to display it on every frame. So let's get to it.

Javascript:

API.OnUpdate.connect(function() {
     
     // We don't want to display the text if the main HUD is not visible
     if(!API.GetHudVisible())
     {
          return;
     }

     // Always check if the entity has the data we plan to access
     if(API.HasEntitySyncedData(API.GetLocalPlayer(), "LEVEL"))
     {
          // Our player has the "LEVEL" data key we set earlier, let's get it's value and display it on the screen
          var level = API.GetEntitySyncedData(API.GetLocalPlayer(), "LEVEL");

          // Get the width of the player's screen resolution so we know where to display the text
          var resX = API.GetScreenResolutionMantainRatio().Width;

          // Draw the level!
          API.DrawText("~r~Your level: ~w~" + level, resX - 30, 150, 0.5, 83, 119, 237, 255, 4, 2, false, true, 0);                    
     }
});

And that's it! The text "Your level: X" (X being the player's level) will be displayed on the player's screen.

Now, remember earlier when we talked about synced EntityData security and how players with modded clients could modify this data? We will now add some security to our code to prevent this, and for this we will use the OnEntityDataChange event along with some other stuff.

As told earlier, you should never store sensitive information on synced EntityData, and a player's level sure is sensitive information. To solve this problem we will need to store the player's level on the server and use the information stored on the server to do stuff with a player's level. The synced EntityData will only be used for the text display.

To also show an use for the normal (local) EntityData which is not synced with the clients, we will be using it now for this example, but you could also use a C# Dictionary for what we're going to do now. So let's modify our code a little bit:

public YourConstructor()
{
     API.OnPlayerFinishedDownload += OnPlayerConnect;
}

private void OnPlayerConnect(Client player)
{
     API.SetEntityData(player.handle, "LOCAL_LEVEL", 0);
     API.SetEntitySyncedData(player.handle, "LEVEL", 0);
}

public void setPlayerLevel(Client player, int level)
{
     API.SetEntityData(player.handle, "LOCAL_LEVEL", level);
     API.SetEntitySyncedData(player.handle, "LEVEL", level);
}

public int getPlayerLevel(Client player)
{
     // Always check if the entity has the data we wan't to access before accessing it
     if(API.HasEntityData(player.handle, "LOCAL_LEVEL"))
     {
          return API.GetEntityData(player.handle, "LOCAL_LEVEL");
     }

     // Just return 0 if it doesn't have the data
     return 0;
}

So now we're storing the level data both locally on the server and we're also still syncing it with the clients. Notice how we're setting the level data locally and also syncing it in the setPlayerLevel function, and how we're only using the normal (non-synced) entity data functions in the getPlayerLevel function. As we learned earlier we should never trust the client when storing sensitive information, so that's the reason we're using the normal (local) EntityData functions in the getPlayerLevel method. Always keep that in mind when working with EntityData.

So with this changes, right now if a player somehow managed to modify the synced data it wouldn't really matter because internally we're using the non-synced data to do stuff with a player's level. It would just modify the level in the text display. But if you also want to prevent that to happen, you could use the OnEntityDataChange event:

public YourConstructor()
{
     API.OnEntityDataChange += OnEntityDataChange;
}

private void OnEntityDataChange(NetHandle entity, string key, object oldValue)
{
     // If the modified key is the player's level
     if(key == "LEVEL")
     {
          // Get the new value it was set to
          int newValue = API.GetEntitySyncedData(entity, key);

          // Get the real player's level value
          int playerLevel = API.GetEntityData(entity, "LOCAL_LEVEL");

          // Never let the synced level be different from the locally stored level
          if(newValue != playerLevel)
          {
               // The synced level is different, so let's set it back to the correct value
               API.SetEntitySyncedData(entity, "LEVEL", playerLevel);
          }
     }
}

The code above will prevent the synced level data key from being different from the locally stored one.

Final notes

EntityData is destroyed when the entity that owns the data is destroyed, so if you store information on a player, it will be automatically destroyed when they disconnect. You can also achieve the same result by using the ResetEntitySyncedData or ResetEntityData depending on if the data is synced or not to destroy EntityData.