How To Cog - Chapter 3.5 - Multiplayer

Multiplayer coding is one of the hardest things to do in JK. The code itself isn't difficult to write. The hard part is making sure your cog is running in sync on all computers. Most well-designed multiplayer cogs use a client / server system so that one cog is designated to keep the other cogs in sync with it.

Multiplayer Flags

To control how your cogs will run in multiplayer games, there are several Cogscript Flags you can use. Take your time to read up on these flags. Cogs do have default flags, and each type of cog has its own defaults. The main flags you'll use are 0x40 and 0x200. 0x80 is also important, but this tutorial doesn't cover MotS yet.

Jedi Knight will sync a lot of things in the game. No one knows exactly what it does sync, but there are obvious things like the player's position (BTW, if the player is not significantly moving, the game won't sync his pos). That type of syncing is stuff that the engine takes care of, cog has nothing to do with that. But other things like thing creation and changing models are synced with other computers only because the verb (FireProjectile() and SetThingModel()) will take care of the syncing for you. Not all verbs will do this, and some verbs don't need to because JK is already syncing whatever it changes.

Because multiplayer testing requires a few computers, and preferably a few people, not that much multiplayer research has been done. So you're pretty much on your own for figuring out what JK syncs and what verbs do the syncing for you - usually you can make a good guess and you'll be right.

The 0x200 flag stops verbs from syncing their changes with other computers. So if you have a cog that's going to run on every client, you don't need it to waste time and bandwidth trying to sync changes with other clients that are doing the same thing. So you'd add 0x200 so that each client cog will only make changes locally.

By default, cogs run only on the server - in singleplayer, the computer is the server. Class and inventory cogs are given the 0x40 flag by default so that they'll run locally on every computer - client and server. This is needed because your weapon cogs, for example, would be useless if they only ran on the server's computer.

A combination of both of these flags (0x240) will make a cog run locally on every computer and not sync its changes. Most multiplayer cogs are given this flag. But if you have a simple server cog that needs to sync its changes (and the verbs you're using can do it), you won't be using either of these flags (0x40 is useless on the server).

There is no good way to find out what flags your cog has in JK. There's no verb to do it with. The only way to guess the purpose of JK's flags is to test in MotS, and since MotS is slightly different, there's not a good way to make sure the flag information is accurate.

Client/Server Versions

Latency is the term used to describe the time it takes for one computer to send information to another. Most people just call this "lag time." High latency can cause a lot of problems for your code because the goal of multiplayer cogs is to sync information between computers. And when you have bad latency (depending on how much you need to sync), the difference between the server's information and the client's can get bad enough to make your patch unplayable.

Say for example that you have exploding barrels that are created on a conveyor belt. This conveyor goes to a bunch of places in your level. One of the things that JK doesn't sync is throwable objects. So if you want your barrels to be the same on all computers, you're going to have to do that manually. And to make it harder, you want players to be able to score by killing other players with the barrels' explosions.

This kind of example is going to require a lot of communication. If the barrels are not in the same places on all computers, then one player may see a barrel blow up right in front of his target (because explosion effects are local) while on the target's computer, the barrel may explode too far away to damage him.

That is how latency causes problems. Suppose you have a latency of 200 milliseconds - this would be 56k dialup connection. This may not seem like much time, but since your barrels are on a conveyor, you're going to see the barrels hopping all over the place as clients receive old positions from the server. You'll see the client's barrel move smoothly down the conveyor, and as soon as your cog receives a trigger with the new position, you'll see the barrel hop somewhere nearby.

And it gets worse. If you're only syncing the position, the barrel may be pushed off the conveyor on a client where its movement will be stopped. But the server will still send it position updates according to the server barrel's movement. Barrels can move a lot in a fifth of a second, so that could look really bad.

So although you have a good idea and it can work pretty well, you'll have to explain that your patch won't work over the internet on slow connections.

Communication

The different methods of communicating between cogs are covered in the communication tutorial, but triggers are covered here also. Triggers are sent out to one or all computers with SendTrigger() and received by a cog that calls its trigger handler. Triggers are sent with an ID so that you can tell them apart. They're your only option for multiplayer comm, but they're not too bad.

Make sure you put plenty of comments beside your triggers. Although this tutorial talks about client-side and server-side code as being in seperate cogs, you can easily put them in the same cog (and many editors do). Take a look at this example:

pulse:
	// pulse is run only by the serving computer.

	for(i=0; i < maxBarrels - 1; i=i+1)
	{
		// just to make the code easier to read.
		pos = GetThingPos(barrel0[i]);
		// send out the trigger to all computers (us too).
		// can't send a whole vector, so we'll break it up.
		SendTrigger(-1, 35, VectorX(pos), VectorY(pos), VectorZ(pos), i);
		// we don't want to flood our network with
		// triggers, so sleep for a very short while.
		Sleep(broadcast_interval);
	}

return;

trigger:
	// if this is a trigger from the local cog, ignore it.
	if(GetSenderRef() == GetSelfCog()) return;

	if(GetSourceRef() == 35)
	{
		// receiving a barrel pos update from a server.
		// first, rebuild the vector.
		pos = VectorSet(GetParam(0), GetParam(1), GetParam(2));
		// now set the local barrel's pos.
		SetThingPos(barrel0[GetParam(3)], pos);
	}

return;

Something not obvious in the example is that the thing numbers of things created in the game after startup will not be the same on all computers. That's why we have to keep an array of all of the barrels we create. And when we send out pos updates, the index number is sent. So as long as we make sure our arrays are set up correctly, there won't be a problem.

This example shows how a cog must be prepared to be run on several different computers. Only the server will use the pulse message, and it will ignore updates that it sends to itself. All of the clients will respond to the trigger and reset their barrels' positions.

Our example cog is assumed to have the 0x40 flag which must be added if it's not going to be an inventory cog. Cog does have a syncing verb for positions, but it doesn't just set the new position - it tries to move them to their new position sometimes making the situation worse.


Previous: Debugging Up: How To Cog Next: Threads
  • Create:
This page was last modified 16:23, 14 April 2006.   This page has been accessed 1,984 times.