Networking Variables
From GMod Wiki
Lua: Networking Variables |
Description: | Learn how to send information from the server to client and vice versa. |
Original Author: | Joudoki |
Created: | 18th June 2008 |
Very often in coding any Lua script, the need will arise for a way to access a variable on both the server and the client; depending on the type of variable, how often it needs to be updated etc, there are various methods of accomplishing this.
Contents |
The first way is to put the variable in a script that's loaded by both the server and the client. This is most common in SWEPs, SENTs, and SNPCs. This method is good for when the variable in question isn't going to be changing after it is initialized.
Things like maximum health, mass, sound paths, etc, are good candidates for this.
Example of this in a SENT's shared.lua:
-- Spawnable by whom? ENT.Spawnable = true; ENT.AdminSpawnable = true; ENT.BulletDamage = .1; -- Any damage taken from bullets will be 1/10 of normal ENT.ExplosiveDamage = 1; ENT.BoostCooldown = 5; -- Time between boosts (in seconds) ENT.JumpCooldown = 3; -- Time between jumps ( in seconds ) - we're not hudini, we don't levitate ENT.UseCooldown = 2; -- How long between when the user enters and when he's allowed to exit. (seconds) ENT.EnterSound = "npc/roller/mine/rmine_reprogram.wav"; -- When someone gets into the ball ENT.ExitSound = "npc/roller/mine/rmine_predetonate.wav"; -- When the controller gets out
To access these variables in either the clientside or the serverside lua for the SENT, all that is needed is to directly reference the variable:
print( self.Spawnable ); damage = self.BulletDamage * dmg; self:EmitSound( self.EnterSound, 100, 100 );
However, when the variable is changed on the clientside, it will only affect the client. If a variable is changed serverside, then the variable will be only be changed on the server:
-- Shared.lua: ENT.Test = "Shared"; if (CLIENT) then -- Do some tests on the client print( self.Test ); -- Output: "Shared" self.Test = "Client"; print( self.Test ); -- Output: "Client" end if (SERVER) then -- Now check if it's changed on the server print( self.Test ); -- Output: "Shared" end
As you can see, this method has limitations, and is generally good for constants.
Server to Client
SetNetworkedVariable should only be used when you want to notify every client about a change. To send a message to a single client use User Messages. |
SetNetworkedVariable
On every entity object, there is a set of method that can be used to store data in that Entity:
( These aren't all of the functions; check the Server Function Dump for others )
Which are all paired up with a matching Get* method:
This method is useful for SENTs in particular; use this method when you have an entity handy to store data in. It is good for variables such as cooldowns, health, etc.
This method has a small limitation; you have to know what type of variable you are sending ( if you are sending an entity, you had better use SetNetworkedEntity ).
Example:
-- where myEnt is the entity we're storing data on -- this is on the SERVER function updateValue( vector ) myEnt:SetNetworkedVector( "myVector", vector ); end -- this is on the CLIENT function doStuff() local myVec = myEnt:GetNetworkedVector( "myVector" ); print( "The vector is: " .. tostring( myVec ) .. "." ); if ( !myVec ) then -- It's nil; either it wasn't set right, there was an error, or something else. print( "The value is nil! Problems." ); else -- We now have a vector print( "We have a vector =D" ); end end
Note that, however, this method is only useful for stuff that's read on a "need to know basis" - aka, information that's only presented when asked for. If you want to constantly update information, and it changes constantly, this method will still work; however, if you want to know what it is constantly, and it doesn't change constantly, then this method isn't the most effective. Take, for example, this:
-- SERVER function updateEnt( myString ) myEnt:SetNetworkedString( "myString", myString ); end lastValue = ""; -- CLIENT function checkString( ) if ( myEnt:GetNetworkedString( "myString" ) != lastValue ) then print( "Value Updated!" ); lastValue = myEnt:GetNetworkedString( "myString" ); doStuff(); end end hook.Add( "Think", "checkForUpdate", checkString );
This method is incredibly inefficient; it is not good when the string only changes every once in a while.
User Messages
When you have a value that only updates every once in a while, it's better to let the client know when the value changes, rather than having the client asking the server constantly if the value has changed. To do this, we use User Messages. User Messages are basically a letter message that is filled with information, then sent to the client. When it gets to the client, a hook is called ( similar to game mode hooks ) that calls the callback function.
Server
To send a message from the server, you first open a message using umsg.Start. Then, you fill the user message with info using the various User Message functions. Finally, you end the message with umsg.End.
Here's an example on the serverside, which sends a message containing a string, an integer, and an entity ( the order here is important ):
function sendStuff( myString, myInt, myEntity ) umsg.Start( "myMessage" ); umsg.String( myString ); umsg.Integer( myInt ); umsg.Entity( myEntity ); umsg.End(); end
As you can see, it's not that difficult to send messages from the server. You may have also noticed that in the umsg.Start function that there is an argument - this is the name of the user message, and will be used to indentify the message when it reaches the client.
There is also a second argument, used to specify which players to send the user message to. There are typically three values that are used:
- Omit: Send the message to all players.
umsg.Start( "myMessage" );
- Player Entity: Send the message to a specific player.
umsg.Start( "myMessage", player.GetByID( 1 ) );
- Recipient Filter: Send the message to a specified group of players.
local rp = RecipientFilter(); rp:AddPlayer( player.GetByID(1) ); rp:AddPlayer( player.GetByID(2) ); umsg.Start( "myMessage", rp );
Client
On the client side, messages are received by hooks - that is, before the message is received, a hook is added that says "When we receive a message called "blah", send it to function "getBlah". This is what this looks like in code:
-- Client -- this is the function to be called function getBlah( um ) print( um:ReadString() ); end -- this is where we hook it. usermessage.Hook("blah", getBlah ); -- Here's the serverside of this: function sendBlah( blahString ) umsg.Start( "blah" ); umsg.String( "Blahblahblah" ); umsg.End(); end
The um argument is actually an object - bf_read. Use the functions of the object to get the messages inside.
player.SendLua
The last method is the ugliest - it basically just sends the client the lua that the server wants it to run. So, if you wanted to send a variable to a client, you could do something like this:
-- value = blah player.GetByID(1):SendLua( "myVariable=\"" .. value .. \"";" );
And the client would get this lua:
myVariable="blah";
Data Tables (From Garry's Blog)
Right now in GMod the entity’s custom variables are networked using an internal usermessage system. When you join the server it sends you a bunch of usermessages containing all the entity’s variables, then after that it sends updates every time the entity changes. This isn’t ideal, but it was the best I could do at the time, since the default network code wasn’t able to handle the amount of variables we needed on some things.
But this presents a problem when dealing with prediction, because the new networked vars might arrive early, or late, and be processed by the wrong tick etc. So networked vars don’t work well for that stuff.
So in the next update I’ve added DTVars, Data Table Vars. The base entity in GMod has a 24 extra variables on it, networked. This is kind of icky, but it’s the best way to do it. There’s 4 of each type available – int, float, vector, angle, ehandle, bool. On the ground level there’s functions for each type to get and set these variables – ent:SetDTBool( int, value ) – ent:GetDTBool( int ).
Higher up, in SENTs, to make things easier, things work like this (this funtion gets automatically called at the right times (in initialize and onrestore))
function ENT:SetupDataTables() self:DTVar( "Int", 0, "Key" ); self:DTVar( "Bool", 0, "On" ); self:DTVar( "Vector", 0, "vecTrack" ); self:DTVar( "Entity", 0, "entTrack" ); self:DTVar( "Entity", 1, "Player" ); end
Then you can access them via -
self.dt.Key self.dt.On self.dt.vecTrack
- just like they were normal variables – except they will be automatically syced between client and server.
I’m recommending that if you can fit all your SENT/SWEP’s data in this variables, then use them. They’re the best option for networked variables. String data, or other variables should use the old method.
Client to Server
Console Commands
The only way for the client to talk to the server is to use console commands; for more information on console commands, see the concommand page.
More Information
- SetNetworked/GetNetworked functions:
- User Messages:
- Send Lua
- Console Commands