net.ReadEntity returning NULL Entity

Been having a little problem with a shared file here. I’m sending an entity from the server to the client and then applying them to the table I’ve made on each client. But for some reason, when I receive the entity clientside it is just null, so my code doesn’t work. Keep in mind that the code wasn’t finished when I ran into this problem, so that’s why you see pointless net.WriteStrings and what not.


if SERVER then

	util.AddNetworkString( "SV_CombAdding" ) -- if a net msg is sent from client then name it cl
	util.AddNetworkString( "CL_SendCombineAct" ) -- if a net msg is sent from server then name in sv
	
	net.Receive( "CL_SendCombineAct", function( len, client )
		print("received")
	end )

	function GAMEMODE:Spawn_NPC( tab )
		if !tab then debug.Trace() end
	
		if tab.ply and tab.class and tab.weapon and tab.pos then
			local ent = ents.Create( tab.class )
			ent:SetPos( tab.pos )
			if tab.angles then ent:SetAngles( tab.angles ) end
			ent:Give( tab.weapon )
			ent:Spawn()
	
			table.insert(tab.ply.CombTab, ent)
			net.Start( "SV_CombAdding" )
				net.WriteEntity( ent )
			net.Send( tab.ply )
		end
	end
	
else

	GM.CL_CombTab = {}

	local function addtocltab( ent )
		table.insert( GAMEMODE.CL_CombTab, ent )
		ent.Name = "Soldier "..#GAMEMODE.CL_CombTab
	end

	net.Receive( "SV_CombAdding", function( len )
		addtocltab( net.ReadEntity() ) -- to keep us "up to date" we simply just network the combine ents in our squad
	end )

	function GM:PreDrawHalos()
		if #GAMEMODE.CL_CombTab > 0 then
			halo.Add( GAMEMODE.CL_CombTab, Color( 255, 0, 0 ), 5, 5, 2 )
		end
	end

	function commandcallback1() -- "move to this position" command
		net.Start( "CL_SendCombineAct" )
			net.WriteString( "MoveIt" )
		net.SendToServer() -- no net.WriteEntity here because we use ply:geteyetrace.entity serverside to make our
						   -- entity move
	end

	local create = vgui.Create
	function Open_CommandMenu()
		if #GAMEMODE.CL_CombTab <= 0 then return end
		local p_Window = create( "DFrame" )
			p_Window:DockPadding( 10, 40, 40, 0 )
			p_Window:LerpPositions( 8, false )
			p_Window:SetPos( ScrW() / 2.0 - 350, ScrH() / 2.0 - 100 )
			p_Window:SetSize( 200, 250 )
			p_Window:SetTitle( "Soldier Commands" ) -- title
			p_Window:SetDraggable( true )
			
		for _, entity in pairs( GAMEMODE.CL_CombTab ) do
			if entity.Name then
				local p_Button = create( "DButton", p_Window )
				p_Button:Dock( TOP )
				p_Button:SetText( entity.Name )
				p_Button.DoClick = function()
					commandcallback1()
				end
			end
		end
		
		p_Window:MakePopup()
		p_Window:DoModal()
		return p_Window
	end
	
end

Correct me if I’m wrong but you have to wait a non-arbitrary time for the entity to be networked and created on clients before they can access it.

For my netwrapper crap, I send the entity index (ent:EntIndex()) and some data to the clients. Using an OnEntityCreated hook on the clients, it uses the entity’s EntIndex to look into a table and see if there is any data there that needs to be applied to it. The reason it works is because the EntIndex is the same across server and client. You just have to make sure to clear any stored data at that index when the entity is removed because EntIndex values get recycled. I.e., spawn an entity and it gets assigned EntIndex = 87. Delete it, spawn another prop, and it gets index 87 again.

You could do something similar, unless someone knows a better way of detecting when a specific entity has initialized on clients.

Would doing something like


net.Receive( "thisisontheclientbtw", function( wedontneedthis )
table.insert(ourdatatable, Entity(net.ReadInt()))
end )

also work, if the integer I sent with the message was the ent’s entindex?

The entity most likely hasn’t been created on the client yet when they receive that message, assuming you’d be sending it right after your ent:Spawn() code. E.g.:



-- server
util.AddNetworkString( "Test" )

hook.Add( "OnEntityCreated", "Test", function( ent ) 

     net.Start( "Test" ) 
         net.WriteUInt( ent:EntIndex(), 16 )
     net.Broadcast() 

end )



-- client
net.Receive( "Test", function( bytes ) 

     local id = net.ReadUInt( 16 )
     print( id, Entity( id ) )
     -- prints the id number and [NULL ENTITY] because the entity hasn't been created on the client yet

end )

A concept you could use is roughly along these lines:



-- server

-- your entity creation code
-- ...
net.Start(<message>) 
     net.WriteUInt( ent:EntIndex(), 16 )
net.Send(<client>) 




-- client
entids = entids or {}

net.Receive(<message>, function( bytes )
    entids[ net.ReadUInt( 16 ) ] = true
end )

-- check entities when they are created to see if they should be added to the table
hook.Add( "OnEntityCreated", "Whatever", function( ent )
    if ( not entids[ ent:EntIndex() ] ) then return end
   
    GAMEMODE.CL_CombTab[ #GAMEMODE.CL_CombTab + 1 ] = ent
end )

-- clear old entity data so that new entities using the same index don't get included
hook.Add( "EntityRemoved", "Whatever", function( ent )
    if ( not entids[ ent:EntIndex() ] ) then return end

    entids[ ent:EntIndex() ] = nil
end )

You realize that is THE EXACT code that Read/WriteEntity does in default gmod, right?

I would use Read/WriteEntity if I didn’t want to show him an example of using EntIndex, which leads into the example I was editing up before you posted.

The code you posted is still terrible! This is the net library, and if you need to send a message to a client with an entity that is probably invalid, then you are doing it wrong.

As I stated in my first post, if you or anyone else knows a better way of doing it, post it. I’m just trying to help the OP with what I’ve learned. If you have a better way, it’ll help me in the end as well.

As far as the client is concerned, the entity doesn’t exist unless it’s within a certain range of it. I know, I know. The good thing is that you can know when the entity is in range by using the entities initialize hook.

What about a networked variable? What about making your own npc and making a render hook add a halo? What about, if you know a class will always be in the list, make a table of classes and check for entities in your predrawhalos hook, and render them?

If you use net like this, you are doing it terribly wrong.

For one, entities don’t initialize on clients when not in pvs. So if the entity gets created and removed before the client sees it, you’re fucked. A solution to this would be exposing the CBaseHandle class and caching those, but that’d still be wrong.

using EntityRemoved on the client is especially unreliable since it gets called on every single clientside entity before (during?) fullupdates, without a matching OnEntityCreated afterward

So… what would be the proper solution? Creating my own custom NPCs isn’t an option because I need to use the combine soldiers. Could I use ply:SetNWEntity serverside to then get the NPC clientside? What I’m trying to do is have a “squad” of combine for every player(represented as a table for that player on the server), so when my GAMEMODE:Spawn_NPC function is called, the player represented by “tab.ply” has that soldier inserted into their clientside table of entities, which I call halo.Add on.