Функции клиента/сервера
Материал из CryWiki Russia
|
Содержание |
Introduction
When we want to script in multiplayer, things get more complex. In singleplayer we do not have to care about connecting clients mid-game, disconnects, client/server achitecture and all that.
But once you want to get into the multiplayer side, everything needs to be considered. Don’t run away yet though, the Crysis lua system is quite flexible. This article will explain how the client/server architecture works in Crysis (lua) and how you can make your own functions execute from server to client or the other way around.
The door
To start things of we’ll have a look at an entity which is already present in Crysis: the door. The file is located at Scripts/Entities/Doors/Door.lua
Net Expose
The first network-related code we see is the Net.Expose function (with a table as argument).
Lua
Net.Expose { Class = Door, ClientMethods = { ClSlide = { RELIABLE_UNORDERED, POST_ATTACH, BOOL }, ClRotate = { RELIABLE_UNORDERED, POST_ATTACH, BOOL, BOOL }, }, ServerMethods = { SvRequestOpen = { RELIABLE_UNORDERED, POST_ATTACH, ENTITYID, BOOL }, }, ServerProperties = { }, };
The first thing in the table is the Class. The class is defined at the top of the file:
Lua
Door = { Properties = { soclasses_SmartObjectClass = "Door", fileModel = "Objects/library/furnishings/doors/toiletstall_door_local.cgf", Sounds = { soundSoundOnMove = "sounds/doors/wooddooropen.wav", soundSoundOnStop = "", soundSoundOnStopClosed = "", fVolume = 200, fRange = 50, }, Rotation = { fSpeed = 200.0, fAcceleration = 500.0, fStopTime = 0.125, fRange = 90, sAxis = "z", bRelativeToUser = 1, sFrontAxis = "y", }, Slide = { fSpeed = 2.0, fAcceleration = 3.0, fStopTime = 0.5, fRange = 0, sAxis = "x", }, fUseDistance = 2.5, bLocked = 0, bSquashPlayers = 0, bActivatePortal = 0, }, PhysParams = { mass = 0, density = 0, }, sounds = {}, Server = {}, Client = {}, }
Keep that in mind when making your own entity multiplayer-compatible. Call Net.Expose AFTER defining your class.
After that, we can see the Client and Server methods
Lua
ClientMethods = { ClSlide = { RELIABLE_UNORDERED, POST_ATTACH, BOOL }, ClRotate = { RELIABLE_UNORDERED, POST_ATTACH, BOOL, BOOL }, }, ServerMethods = { SvRequestOpen = { RELIABLE_UNORDERED, POST_ATTACH, ENTITYID, BOOL }, },
In here we define what functions can get called over the network.
- ClientMethods
- These functions are executed on the client, requested by the server.
- ServerMethods
- The functions in this table will be executed on the server, requested by the client.
Now let’s take a closer look at what info the Net.Expose function needs for these functions.
FunctionName = { RELIABILITY, ATTACHMENT, FUNCTIONARGS },
- Function name
The first thing it needs is a function name. The unwritten rule is to prefix client-functions with Cl and server-functions with Sv.
- Reliability Type
- RELIABLE_ORDERED
- Ensures the packets are delivered, and in the correct order.
- RELIABLE_UNORDERED
- Ensures the packets are delivered, but with the possibility of receiving them in a different order.
- UNRELIABLE_ORDERED
- Does not ensure that the packets are delivered, but makes sure they’re in the correct order if they are.
Примечание:
It seems that UNRELIABLE_UNORDERED is not defined. However if you really need to use UNRELIABLE_UNORDERED, use the number 3 instead (The reliability defines are just numbers).
The most used one is RELIABLE_UNORDERED. This will work just fine for most functions. If you’re repeatidly sending the same function and it needs to arrive in order, use RELIABLE_ORDERED.
- Attachment Type
- NO_ATTACH
- POST_ATTACH
- PRE_ATTACH
- Function argument types
The last thing to do is listing the function arguments that need to be passed on the network. Possibilities are:
- BOOL
- A boolean value (true/false)
- STRING
- A string
- INT8
- An integer of 8 bits
- INT32
- An integer of 32 bits
- VEC3
- A vector table (with x, y and z values)
- ENTITYID
- An entityId value
- FLOAT
- A floating point value
- STRINGTABLE
- A table with strings
- DEPENTITYID
- ???
There are probably more around, but those are the ones that are used the most.
Just keep this in mind:
Предупреждение:
When you want to execute functions from the server to the client, or the other way around, the functions need to be listed in a Net.Expose function (for Lua).
Function implementations
Let’s have a look at the actual functions now. How do they work and how do you call them?
A server function: SvRequestOpen
Lua
function Door.Server:SvRequestOpen(userId, open) local mode=DOOR_TOGGGLE; if (open) then mode=DOOR_OPEN; else mode=DOOR_CLOSE; end local user=System.GetEntity(userId); self:Open(user, mode); end
The first thing to note is the .Server just behind the ClassName. The Server part is a table in the Door class (table) generated by the Crysis Lua (Network) system.
The function has to be in the Server table because we said it would be a server function in Net.Expose.
Lua
ServerMethods = { SvRequestOpen = { RELIABLE_UNORDERED, POST_ATTACH, ENTITYID, BOOL }, },
From this info, we know the first argument will be an entityid, and the second will be a boolean. If we go deeper inside the function we can see it sets the door to open or closed, depending on the argument, and then calls the Open() function. Let’s take a look at that.
Lua
function Door:Open(user, mode) local lastAction = self.action; if (mode == DOOR_TOGGLE) then if (self.action == DOOR_OPEN) then self.action = DOOR_CLOSE; else self.action = DOOR_OPEN; end else self.action = mode; end if (lastAction == self.action) then return 0; end if (self.Properties.Rotation.fRange ~= 0) then local open=false; local fwd=true; if (self.action == DOOR_OPEN) then if (user and (tonumber(self.Properties.Rotation.bRelativeToUser) ~=0)) then local userForward=g_Vectors.temp_v2; local myPos=self:GetWorldPos(g_Vectors.temp_v3); local userPos=user:GetWorldPos(g_Vectors.temp_v4); SubVectors(userForward,myPos,userPos); NormalizeVector(userForward); local dot = dotproduct3d(self.frontAxis, userForward); if (dot<0) then fwd=false; end end open=true; end self.fwd=fwd; self:Rotate(open, fwd); self.allClients:ClRotate(open, fwd); end ... return 1; end
A lot of code here, but we will just look at the essentials. The function first sets the correct action (door state) and returns if it was the same as last time (ie: opening a door twice). Then it checks if there’s a Range value in the Door.Properties.Rotation table. If that’s the case, we have a rotating door. Next, it calls the Rotate() function, which will actually do the rotation (We haven’t done any rotating so far).
Finally we send the new door status to all clients (the door has to open/close), so every connected client can see the door opening, with
Lua
self.allClients:ClRotate(open, fwd);
This is how we execute a function from the server -> client. We want all clients to execute the ClRotate function with the supplied arguments.
Instead of sending it to every client, there are also ways to limit the number of clients to send it to.
- To all clients
Lua
self.allClients:ClRotate(open, fwd);
- To all clients, except 1
Lua
self.otherClients:ClRotate(channelId, open, fwd);
- To 1 client
Lua
self.onClient:ClRotate(channelId, open, fwd);
Примечание:
channelId is a network channel number, which you can easily get from a player with playerEnt.actor:GetChannel()
To send a function to only 1 specific team, you’ll have to loop through all players and check their team, and then send a command to them individually, using the method above.
It’s time to look at the ClRotate function now.
A client function: ClRotate
Lua
function Door.Client:ClRotate(open, fwd) if (not self.isServer or g_gameRules.game:IsDemoMode() ~= 0) then if (open) then self.action=DOOR_OPEN; else self.action=DOOR_CLOSE; end self:Rotate(open, fwd); end end
As with the server function, we can see what the arguments will bring in Net.Expose: 2 booleans. The function itself is pretty simple, first we check if we are not a server (this is a common thing to do, we do not want the code to be executed twice on it) or if we are in demoplayback-mode. Then the action is set and self:Rotate() is called.
In the Door:Rotate() function we do the actual turning of the door, which is not in the scope of this article.
The final thing you might wonder is: When and how has initiated the SvRequestOpen function? A search in the file leads us to the Door:OnUsed() function.
Lua
function Door:OnUsed(user) self.server:SvRequestOpen(user.id, self.action~=DOOR_OPEN); end
The OnUsed function gets called by the lua system when using an entity (in this case, the door). So when a client (not necessarily you) uses a door, he will send a request to open the door, to the server.
You can see the syntax for client->server is even more simple than server->client. We just have to add a .server part and we’re good to go.
Предупреждение:
Do not forget Lua is case sensitive. If you use .Server instead of .server, it won't work.
Schematic form
Handling connecting clients
The final thing we have to consider is clients connecting in the middle of the game. Say client A opens a door. After a while client B connects. If client B runs up to the door he will see a closed door, since he didn’t get the 'open' message from the server, which was sent at the time client A opened the door.
To solve this there’s a function which gets called when a client connects: OnInitClient.
Lua
function Door.Server:OnInitClient(channelId) if (self.Properties.Rotation.fRange ~= 0) then local open=self.action == DOOR_OPEN; self.onClient:ClRotate(channelId, open, self.fwd or false); end if (self.Properties.Slide.fRange ~= 0) then local open=(self.action == DOOR_OPEN); self.onClient:ClSlide(channelId, open); end end
When a client connects, this function gets called. It just sends the current state of the door to the client. So if client B now runs up to the door, he should see it’s open.