Working with Players Client-side

In my gamemode, I have several bits of data for players that I then sync to clients when they first join, or when that data changes, via Net messages. I have begun to notice though that on the CLIENT side, sometimes Players are NULL / IsValid returns false for them. I’m assuming this is due to PVS. My issue is, especially on initial join, the player isn’t valid because they aren’t nearby, and so the data comes down but never gets attached to the player or anything because IsValid returns false.

So I guess I have 2 questions:

  • How do ‘clientside’ Players work? If you attach a var to a player clientside, and then a little later, the player becomes NULL because it isn’t in PVS, and then later they come back into PVS, do they still have the variable attached? I’m assuming this does work somehow, otherwise my gamemode would be extremely broken.

  • Is there a better way to be storing data so that it isn’t attached to the player itself? Should I put all my data in a separate global table clientside keyed to SteamID or some sort of player index?

My alternative of course is to just fetch the data on-demand if for some reason the initial sync didn’t go through; so once the player gets near, send a Net message to grab the extra data at that point.

Which hook sends the data to the client? InitialSpawn, OnConnect or directly the Initialize hook?

Also, you could as example, send the data to the client & save it in the data folder/use PData, but if you save it on the client, they could edit it, as example in the data folder, so attaching it is still good. I would suggest using the InitialSpawn hook, & using timer.Simple to wait some seconds, like 3, some addons do that to prevent such errors to happen, so the player actually fully initialized.

To start the initial syncing process, I actually have a hook on the clientside for CreateEntity that checks to see if the created entity is equal to the LocalPlayer; if it is, then I send a net message to the server saying “I’m ready to get my initial dataload!”

The issue isn’t the local player itself though; the issue is OTHER players. So if I’m on with 2 other players, who are on the other side of the map, I send the data for those 2 players from the server to my client, but because on the client they are so far away, CLIENTSIDE those players I send aren’t valid.

Perhaps the issue is that I’m not sure the best way to “target” a specific clientside player from the server. There are so many ways to identify a player; UserID, UniqueID, SteamID, which may be better to do, but I can’t seem to find a consistent one between server and client that isn’t just based on Steam ID.

Currently in the syncing process, I am sending the player entity itself from server to client, but my guess is that because the client isn’t transmitting that player’s entity because they’re so far, that’s why it blows up.

Example of how I’m syncing other players to the newly loaded client:


function SyncPlayers(ply)    
    local playerinfo = {}
    for k, v in pairs(player.GetAll()) do
        if v != ply and v.Loaded and v.CharName then 
            table.insert(playerinfo, {
                entity = v,
                title = v.Title,
                faction = v.Faction,
                factionstatus = v.FactionStatus,
                charname = v.CharName,
                guildname = v.GuildName,
                guildtag = v.GuildTag,
                badges = v.Data.Badges or {},
                health = v:GetHealth(),
                action = v:GetAction(),
                mind = v:GetMind(),
                maxhealth = v:GetMaximumHealth(),
                maxaction = v:GetMaximumAction(),
                maxmind = v:GetMaximumMind(),
                bio = v:GetBio(),


net.Receive("NETLoadPlayers", function(len)        
        local playerdata = net.ReadTable()
        timer.Simple(1, function()
            for k,v in pairs(playerdata or {}) do
                local ply = v.entity
                if ply and IsValid(ply) then
                    ply.Title = v.title
                    ply.Faction = v.faction
                    ply.FactionStatus = v.factionstatus
                    ply.CharName = v.charname
                    ply.GuildName = v.guildname
                    ply.GuildTag = v.guildtag
                    ply.Bio =
                    if !ply.Data then ply.Data = {} end
                    if ply.Data then
                        if !ply.Data.Badges then ply.Data.Badges = {} end
                        ply.Data.Badges = v.badges

the check “if v != ply” simply replace it with if !v:IsBot() so bots get excluded, then run SyncPlayers inside PlayerInitialSpawn(wait 3 seconds in it with timer.Simple), and see if that works.

edit: actually keep if v != ply, but remove the rest in that line and replace it.

hook.Add("PlayerInitialSpawn", "SyncPlayers", function(ply)
	timer.Simple(3, SyncPlayers(ply))

Good point; I suppose I shouldn’t be assuming that just because the local player has been created, that everything else has been initially created clientside. I’ll move the sync logic into InitialSpawn and give it a shot.