Функции клиента/сервера

Материал из CryWiki Russia

Версия от 20:25, 23 декабря 2010; Alex626 (Обсуждение | вклад)
(разн.) ← Предыдущая | Текущая версия (разн.) | Следующая → (разн.)
Перейти к: навигация, поиск
О статье



Содержание

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

ClientServer Door.jpg

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.