Trying to understand how Network works for entities

Hey there,

I’m trying to understand how Network IDs of entities work. I found with the help of @tellurium the functions NetworkGetNetworkIdFromEntity and NetworkGetEntityFromNetworkId and decided to test them.

My debugging process is simple. I have 2 players (Let’s call them Bob and Steve). On a debug client script, I have this :

if IsControlJustPressed(1, 168) then
	local model = GetHashKey("prototipo")
	RequestModel(model)
	while not HasModelLoaded(model) do
		Citizen.Wait(0)
	end
	local vehicle = CreateVehicle(model, x, y, z, h, true, true)
	SetModelAsNoLongerNeeded(model)
	SetVehicleOnGroundProperly(vehicle)
	SetVehicleHasBeenOwnedByPlayer(GetPlayerPed(-1), true)
end

Now Bob can spawn a fancy X80 by using F7.

I also have this on F6 :

if IsControlJustPressed(1, 167) then
	local ped = GetPlayerPed(-1)
	if IsPedInAnyVehicle(ped, false) then
		local vehicle = GetVehiclePedIsIn(GetPlayerPed(-1), true)
		local netID = NetworkGetNetworkIdFromEntity(vehicle)
		local entityFromNetID = NetworkGetEntityFromNetworkId(netID)
		Citizen.Trace(vehicle .. " | " .. netID .. " | " .. entityFromNetID)
	end
end

With those 2, Bob and Steve can now try figuring out NetworkIDs. So, first Bob spawns an X80, gets in it, and presses F6. Here’s the result for him :

3330 | 16711681 | 3330

Which is the expected result. Now if Bob leaves the car, Steve gets in, and presses F6, here’s the result :

1026 | 16711681 | 0

Now Steve’s local ID is different, which is also normal. Also, the NetID is also the same, and that’s good. The problem is, why is the third result not the same as the first ?

And if it’s an expected result, is there another way to get an entity ID from a netID ?


Let’s try another situation. Let’s say Bob spawns a vehicle, that vehicle’s netID is 16711681, then spawns another one, that second vehicle’s netID is 16711682. It increased by one, nothing unexpected. Now let’s make Steve spawn a vehicle; That third vehicle’s (spawned by Steve this time) netID is 16711681 and the first spawned vehicle by Bob disappeared. My question, is why ?

On the technical level, both players have the same netID which is good, but both players also have different counters for their NetIDs. This means that if Bob and Steve repeatedly press F7 one after the other 10 times, there would only be 10 vehicles instead of an expected 20.


EDIT
Let’s change the second boolean of CreateVehicle to false, and let’s try again. Now the disappearing issue is not a problem anymore, both vehicles stay. This also means that we now have 2 vehicles with the same NetID.The NetIDs are still acting weird tho. Now if Bob spawns 2 vehicles, here are the results :

Vehicle 1 : 11010 | 1 | 11010
Vehicle 2 : 11266 | 2 | 11266

Which is expected. Now if Steve spawns a vehicle and Bob gets in it, here’s the result :

12034 | 1 | 11010

This means that Bob gets the result of his first spawned vehicle, even tho he’s in Steve’s vehicle.


Here’s some more testing following @ejb1123 's answer concerning NetworkRequestControlOfNetworkId.
So, to test this out, I’ve made another function :

if IsControlJustPressed(1, 166) then
	local ped = GetPlayerPed(-1)
	if IsPedInAnyVehicle(ped, atGetIn) then
		local vehicle = GetVehiclePedIsIn(GetPlayerPed(-1), false)
		local netId = NetworkGetNetworkIdFromEntity(vehicle)
		if NetworkHasControlOfEntity(vehicle) then
			Citizen.Trace("You have control of this ENTITY")
		else
			Citizen.Trace("You DON'T have control of this ENTITY")
		end
		if NetworkHasControlOfNetworkId(netId) then
			Citizen.Trace("You have control of this NETID")
		else
			Citizen.Trace("You DON'T have control of this NETID")

			Citizen.Trace("Requesting Control...")
			NetworkRequestControlOfNetworkId(netId)
			while not NetworkHasControlOfNetworkId(netId) do
				Citizen.Wait(0)
			end

			if NetworkHasControlOfNetworkId(netId) then
				Citizen.Trace("You now have control !")
			else
				Citizen.Trace("This should not come up, ever.")
			end
		end
	end
end

Bob and Steve can now Request for control over a NetID. Now let’s say Bob spawns a vehicle, and Steve gets in it; If Steve presses F6, the result are as usual :

5634 | 0 | 0

Which I still don’t understand, (that’s the point lel) but it makes sense with the results from previous tests since Steve never spawned a vehicle, his counter is still at 0.

Now let’s make Steve press F5 and here are the results :

You have control of this ENTITY
You DON'T have control of this NETID
Requesting Control...

And it hangs. My guess is that it’s stuck in the while loop, as NetworkHasControlOfNetworkId never returns true. Other inputs didn’t work, since the script crashed. From here, I’ve tried 2 things :

  • First, I tried commenting that loop, and just seeing if Steve gets the control right away, and no surprise, he never gets it. I also waited to see if Steve gets the control at some point, didn’t work either. During the wait, I also checked frequently if the result from F6 changed, it never did.
  • Second, I tried making Bob disconnect in that infinite loop, to see if Steve gets Control because Bob just doesn’t exist anymore; nope, still no control, still stuck in the loop.

Note : For Bob it says that Bob has control on both NetID and Entity


If anyone has an explanation for this, I’m open to any suggestion.

Thanks,
Exorion.

3 Likes

Have you tried to request control of the vehicle? then see if the results are different?
Here is my tests that I have done.

1 Like

I’m gonna try and edit my post.

Also, I’ve added a third section with more insight on the first situation.

maybe this NETWORK_REGISTER_ENTITY_AS_NETWORKED would make the netID not overlap

I’ve tried using it, it throws an error saying it’s a nil value. natives.lua doesn’t have it. Is this a C# only thing or do I need to call it with Citizen.InvokeNative ?

You might have to call it like this N_0x06faacd625d80caa(p0)

1 Like

Thanks, it works.

Same issue tho, the NetIDs still overlap.

what about after creating the vehicle you call this:

int VEH_TO_NET(Vehicle vehicle) // B4C94523F023419C F17634EB calls from vehicle to net.

VehToNet(vehicle) and using the return of this as the id.

just throwing spaghetti at the wall to see what is going to stick.

I’ll edit this answer when I get the results, it sounds promising.

Edit : VehToNet seems to give the exact same results than NetworkGetEntityFromNetworkId. Seems to be an alias for vehicles only or something. I’ll continue my trials on Request Control.

I just added another section on my trials with Control Request.

1 Like

I have no idea. But this is things I’d look into.

I thought this code was interesting last night. Never seen it used before.

RequestCollisionAtCoord(spawn.x, spawn.y, spawn.z)
while not HasCollisionLoadedAroundEntity(ped) do
Citizen.Wait(0)
end

Seems to be related to this

There is a collision for models native. HAS_COLLISION_FOR_MODEL_LOADED(Any p0) // 22CCA434E368F03A 41A094F8

It seems clear that something hasn’t finished sync’ing or updating, and there seem to be natives to check on that. HasModelLoaded is local for the client. SetVehicleOnGroundProperly is also local, it just burns through that and updates the server. But the client never waits back for a valid response about what it’s creating.

Some random FiveM dev post in 2015

“also: don’t use LOAD_ALL_OBJECTS_NOW, use LOAD_SCENE or REQUEST_COLLISION_AT_COORD and wait for the streamer to load the scene at runtime (especially important if networked, since various subsystems won’t get serviced during a regular LOAD_SCENE - original scripts use an unknown native (formerly START_LOAD_SCENE) combined with NETWORK_UPDATE_LOAD_SCENE and call a bunch of servicing funcs in the middle, but you typically don’t have access to the original script code)”

Basically making a vehicle is local on the clients side, it has nothing to do with multiplayer or the server. Somewhere after that gets updated/synced but there’s no check within createvehicle to stop/valid it. I’d guess your script isn’t waiting for everything to sync before it starts doing stuff with it or getting IDs. The reason the collision check is important I guess in order to do that it needs to sync with the network and that process validates some shit?

3 Likes

This is really good, thanks a lot. I’ll try some stuff, and report back.

1 Like

This code from act_cinema.c looks interesting

bool func_310(var uParam0, int iParam1, vector3 vParam2, float fParam5, int iParam6, int iParam7, int iParam8, int iParam9, int iParam10, int iParam11, int iParam12)//Position - 0x4655F
{
	int iVar0;
	
	if (!network::can_register_mission_vehicles(1))
	{
		return false;
	}
	if (iParam11)
	{
		gameplay::clear_area_of_vehicles(vParam2, 1f, 0, 0, 1, 1, true);
	}
	iVar0 = vehicle::create_vehicle(iParam1, vParam2, fParam5, iParam7, iParam6);
	*uParam0 = network::veh_to_net(iVar0);
	if (network::network_does_network_id_exist(*uParam0))
	{
		entity::_0x3910051CCECDB00C(iVar0, iParam10);
		if (network::_0xC7827959479DCC78(iVar0))
		{
			if (iParam8)
			{
				network::set_network_id_exists_on_all_machines(*uParam0, 1);
			}
			else
			{
				network::set_network_id_exists_on_all_machines(*uParam0, 0);
			}
		}
		vehicle::set_vehicle_is_stolen(iVar0, iParam9);
		unk_0xB2E0C0D6922D31F2(iVar0, 1);
		if (iParam12)
		{
			unk_0xFC40CBF7B90CA77C(iVar0);
			vehicle::set_vehicle_door_control(iVar0, 5, 5, 1f);
		}
		return true;
	}
	return false;
}

Oh crap, this is great. It seems that VehToNet actually gives the entity to the network and returns the newly created NetID or something ?

I’m also gonna try those unnamed natives

1 Like

VEH_TO_NET should be equivalent to certain arguments during object creation - however network IDs got more complex in V compared to IV.

(in fact, VEH_TO_NET is implemented using the exact same function as NETWORK_GET_NETWORK_ID_FROM_ENTITY, it is merely an alias - same for NET_TO_VEH being the exact same as NETWORK_GET_ENTITY_FROM_NETWORK_ID)

Does NET_TO_VEH maybe return a valid network object ID?

1 Like

First unk
ntity::_0x3910051CCECDB00C(iVar0, iParam10);
_0x3910051CCECDB00C = void _SET_ENTITY_REGISTER(Entity entity, BOOL toggle) // 3910051CCECDB00C D3850671

Second unk:
_0xC7827959479DCC78(iVar0)
BOOL NETWORK_GET_ENTITY_IS_NETWORKED(Entity entity) // C7827959479DCC78 D7F934F4

1 Like

Spawning an entity with TRUE, FALSE will apparently only register a local network object, whereas TRUE, TRUE performs extra logic to reserve an object on the network, and register it with the object manager.

Obtaining network entities seems to be doing things related to CGameScriptHandlerNetwork - this is slightly concerning…

1 Like

Ok i will be that guy, why is it concerning?

1 Like

It returns only entities that have been registered with the current script handler, apparently. It is not known if the game will do so automatically… it might be meant to do so using proper ‘networked scripts’ with fixed names.

1 Like

Did you only test this on spawned vehicles or also on ped vehicles?