CHAPTER 6
NETWORK

Although little emphasis was given to the subject in recent chapters, a key feature of working with Torque 3D is the fact that it was built around a client/server networking architecture.

Torque 3D creates a GameConnection object, which is the primary mechanism that links the client (and the player) to the server. The GameConnection object is built from a NetworkConnection object. When the server needs to update clients, or when it receives updates from clients, the work is done through the good auspices of the NetworkConnection, and it is normally quite transparent at the game level.

What this means in practical terms is that the engine automatically handles things like movement and state changes or property changes of objects that populate a game world. Game programmers (like you and me) can then poke their grubby little fingers into this system to make it do their bidding without needing to worry about all the rest of the stuff, which Torque 3D will manage—unless we decide to mess around with that too!

I know this seems a bit vague, so in this chapter we will attack the nitty-gritty so that you can really see how to use Torque 3D’s built-in networking to the best advantage.

First, we will discuss the features, and look at examples of how they can be implemented, and then later in the chapter, after you update your Emaga sample program, you can try them out.

DIRECT MESSAGING

The quickest way to get down and dirty with the client/server networking in Torque 3D is to use the CommandToServer and CommandToClient direct messaging functions. These extremely useful “ad hoc” messaging functions are used for a wide variety of purposes in a Torque 3D game, like in-game chat, system messages, and client/server synchronization.

CommandToServer

The CommandToServer function is used to send a message from a client to a server. Of course, the server needs to know that the message is coming and how to parse it to extract the data. The syntax is as follows:

CommandToServer(function [,arg1,...argn])

image

An example of how to use this function would be a simple global chat macro capability where a player would press a key, and then a specific message would be broadcast to all other players. Here is how that would work. First, we would bind a key combination to a specific function—say, bind Ctrl+H to the function we’ll call Send-Macro(). In the key binding statement, we’ll make sure to pass the value 1 as a parameter to SendMacro().

SendMacro() would be defined on the client as this:

function SendMacro(%value)
{
  switch$ (%value)
  {
     case 1:
       %msg = "Hello World!";
     case 2:
       %msg = "Hello? Is this thing on?";
     default:
       %msg = "Nevermind!";
  }
  CommandToServer('TellEveryone', %msg);
}

So now, when the player presses Ctrl+H, the SendMacro() function is called, with its %value parameter set to 1. In SendMacro(), the %value parameter is examined by the switch$ statement and sent to case 1:, where the variable %msg is stuffed with the string “Hello World!”. Then CommandToServer is called with the first parameter set to the tagged string “TellEveryone” and the second parameter set to our message.

Now here is where some of the Torque 3D client/server magic elbows its way onto the stage. The client will already have a GameConnection to the server and so will already know where to send the message. In order to act on our message, the server side needs us to define the TellEveryone message handler, which is really just a special-purpose function, that would look something like this:

function ServerCmdTellEveryone(%client,%msg)
{
  TellAll(%client,%msg);
}

Notice the prefix ServerCmd. When the server receives a message from the client via the CommandToServer() function, it will look in its message handle list, which is a list of functions that have the ServerCmd prefix, and find the one that matches ServerCmdTellEveryone. It then calls that function, setting the first parameter to the GameConnection handle of the client that sent the message. It then sets the rest of the parameters to be the parameters passed in the message from the client, which in this case is %msg stuffed with the string “Hello World!”.

This code would look good at the end of the server.cs in the server folder tree.

Then we can do what we want with the incoming message. In this case we want to send the message to all the other clients that are connected to the server, and we’ll do that by calling the TellAll() function. Now we could put the code right here in our ServerCmdTellEveryone message handler, but it is a better design approach to break the code out into its own independent function. We’ll cover how to do this in the next section.

CommandToClient

Okay, here we are—we’re the server, and we’ve received a message from a client. We’ve figured out that the message is the TellEveryone message, we know which client sent it, and we have a string that came along with the message. What we need to do now is define the TellAll() function, so here is what it could look like:

function TellAll( %sender, %msg)
{
   %count = ClientGroup.getCount();
   for ( %i = 0; %i < %count; %i++ )
   {
     %client = ClientGroup.getObject(%i);
     CommandToClient(%client,'TellMessage', %sender, %msg);
 }
}

Our intention here is to forward the message to all the clients. Whenever a client connects to the server, its GameConnection handle is added to the ClientGroup’s internal list. We can use the ClientGroup’s method getCount to tell us how many clients are connected. ClientGroup also has other useful methods, and one of them—the getObject method—will give us the GameConnection handle of a client, if we tell it the index number we are interested in.

If you want to test these example functions, I’ll show you how to do that toward the end of the chapter. If you feel like giving it a go by yourself, here’s a small hint: the commandToClient function is called from the server side, and the commandToServer functions belong on the client side.

As you can see, commandToClient is basically the server-side analogue to commandToServer. The syntax is as follows:

CommandToClient(client, function [,arg1,...argn])

image

The primary difference is that although the client already knew how to contact the server when using CommandToServer, the same is not true for the server when using CommandToClient. It needs to know which client to send the message to each time it sends the message. So the simple approach is to iterate through the ClientGroup using the for loop, getting the handle for each client, and then sending each client a message using the CommandToClient() function, by specifying the client handle as the first parameter. The second parameter is the name of the message handler on the client side this time. Yup—works the same going that way as it did coming this way! Of course, the third parameter is the actual message to be passed.

So we need that message handler to be defined back over on the client. You can do it like this:

function clientCmdTellMessage(%sender, %msgString)
{
  // blah blah blah
}

Notice that when we called this function there were four parameters, but our definition only has two in the parameter list. Well, the first parameter was the client handle, and because we are on the client, Torque 3D strips that out for us. The second parameter was the message handler identifier, which was stripped out after Torque 3D located the handler function and sent the program execution here. So the next parameter is the sender, which is the client that started this whole snowball rolling, way back when. The last parameter is, finally, the actual message.

I’ll leave it up to you to decide what to do with the message. The point here was to show this powerful messaging system in operation. You can use it for almost anything you want.

Direct Messaging Wrap-up

CommandToServer and CommandToClient are two sides of the same direct messaging coin and give us, as game programmers, a tremendous ability to send messages back and forth between the game client and the game server.

Direct messaging can also be an important tool in the fight against online cheating in your game. You can, in theory and in practice, require all user inputs to go to the server for approval before executing any code on the client. Even things like changing setup options on the client—which are not normally the sort of thing that servers would control—can be easily programmed to require server control using the technique we just looked at.

The actual amount of server-side control you employ will be dictated by both available bandwidth and server-side processing power. There is a lot that can be done, but it is a never-ending series of trade-offs to find the right balance.

TRIGGERS

Right off the bat, there is potential for confusion when discussing the term trigger in Torque 3D, so let’s get that out of the way. There are four kinds of triggers that people talk about when programming with Torque 3D:

Image area triggers

Image animation triggers

Image weapon state triggers

Image player event control triggers

I’ll introduce you to all four here, but we’ll talk about three of them—area triggers, animation triggers, and weapon state triggers—in more detail in future chapters.

Area Triggers

Area triggers are special in-game constructs. An area in the 3D world of a game is defined as a trigger object with a bounding volume. When a player’s avatar enters the bounds of the trigger area, an event message is posted on the server. We can write handlers to be activated by these messages, a topic that we will be covering in more depth in Chapter 22.

Animation Triggers

Animation triggers are used to synchronize footstep sounds with walking animations in player models. Modeling tools that support animation triggers have ways of tagging frames of animation sequences. The tags tell the game engine that certain things should happen when this frame of an animation is being displayed. We’ll discuss these later—in Chapter 14.

Weapon State Triggers

Torque 3D uses weapon state triggers for managing and manipulating weapon states. These triggers—identical in function to a finite state machine’s signals—dictate what to do when a weapon is firing, reloading, recoiling, and so on. We’ll look at this in more detail later, in Chapter 20 in the section “Weapon Sounds.”

Player Event Control Triggers

Finally, there are player event control triggers (also called move triggers), which are a form of indirect messaging of interest to us in this chapter. These mechanisms are used to process certain player inputs on the client in real time. You can have up to six of these triggers, each held by a variable with the prefix $mvTriggerCountn (where n is an index number from 0 to 5).

When we use a trigger move event, we increment the appropriate $mvTriggerCountn variable on the client side. This change in value causes an update message to be sent back to the server. The server will process these changes in the context of our control object, which is usually our player’s avatar. After the server acts on the trigger, it decrements its count. If the count is nonzero, it acts again when it gets the next change in its internal scheduling algorithm. In this way we can initiate these trigger events by incrementing the variable as much as we want (up to a maximum of 255 times), without having to wait and see if the server has acted on the events. They are just automatically queued up for us via the $mvTriggerCountn variable mechanism.

Table 6.1 Default Player Event Control Triggers

image

Torque 3D has default support for the first four control triggers built into its player and vehicle classes (see Table 6.1).

In the server control code, we can put a trigger handler in our player’s avatar for any of these triggers that override the default action. We define a trigger handler like this:

function MyAvatarClass::onTrigger(%this, %obj, %triggerNum, %val)
{
   // trigger activity here
  $switch(%triggerNum)
 {
   case 0:
     //replacement for the "fire" action.
   case 1:
     //replacement for the "alt fire" action.
   case 2:
    //replacement for the "jump" action.
   case 3:
    //replacement for the "jetting" action.
   case 4:
    //whatever you like
   case 5:
    //whatever you like
 }
}

The MyAvatarClass class is whatever you have defined in your player avatar’s data-block using the following statement:

    className = MyAvatarClass;

To use a trigger handler, you merely have to increment a player event control trigger on the client, something like this:

function mouseFire(%val)
{
  $mvTriggerCount0++;
}

or this:

function altFire(%val)
{
  $mvTriggerCount1++;
}

GAMECONNECTION MESSAGES

Most of the other kinds of messages used when making a game with Torque 3D are handled automatically. However, in addition to the direct messaging techniques we just looked at, there are other more indirect messaging capabilities available to the Torque 3D game developer. These are messages related to the GameConnection object.

I call these methods indirect because we, as programmers, don’t get to use them in any old way of our choosing. But we can, nonetheless, use these methods, in the form of message handlers, when the Torque 3D Engine decides it needs to send the messages.

What GameConnection Messages Do

GameConnection messages are of great importance to us during the negotiation process that takes place between the client and server when a client joins a game. They are network messages with game-specific uses, as opposed to being potentially more general-purpose network messages.

Torque 3D calls a number of GameConnection message handlers at different times during the process of establishing, maintaining, and dropping game-related connections. In the Torque 3D demo software, many of these handlers are defined in the common code base, whereas others aren’t used at all. You are encouraged to override the common code message handlers with your own GameConnection message handlers or use the unused handlers, if you need to.

Specifics

During program execution, the client will at some point try to connect to the server using a set of function calls like this:

   %conn = new GameConnection(ServerConnection);
   %conn.SetConnectArgs(%username);
   %conn.Connect();

In this example the %conn variable holds the handle to the GameConnection. The Connect() function call initiates a series of network transactions that culminate at the server with a call to the GameConnection::OnConnect handler.

The following descriptions are listed roughly in the order that the functions are used in the Emaga6 program.

onConnectionRequest()

image

This handler is used to check if the server-player capacity has been exceeded. If not exceeded, then “” is returned, which allows the connection process to continue. If the server is full, then CR_SERVERFULL is sent back. Returning any value other than “” will cause an error condition to be propagated back through the engine and sent to the client as a call to the handler GameConnection:: onConnectRequestRejected. Any arguments that were passed to GameConnection::Connect are also passed to this handler by the engine.

onConnectionAccepted(handle)

image

This handler is a good place to make last-minute preparations for a connected session.

onConnect(client, name)

image

In this case the second parameter (%name) is the value the client has used, while establishing the connection, as the parameter to the %(GameConnection). SetConnectArgs(%username) call.

onConnectRequestTimedOut(handle)

image

When this gets called you probably want to display, or at least log, some message indicating that the connection has been lost because of a timeout.

onConnectionTimedOut(handle)

image

When this gets called you probably want to display, or at least log, some message indicating that the connection has been lost because of a timeout.

onConnectionDropped(handle, reason)

image

When this gets called you probably want to display, or at least log, some message indicating that the connection has been lost because of a timeout.

onConnectRequestRejected(handle, reason)

image

When this gets called you probably want to display, or at least log, some message indicating that the connection has been lost.

Table 6.2 Connection Request Rejection Codes

image

onConnectionError(handle, errorString)

image

onDrop(handle, reason)

image

initialControlSet(handle)

image

setLagIcon(handle, state)

image

onDataBlocksDone(handle, sequence)

image

Use this handler to manage the mission loading process and any other activity that transfers datablocks.

onDataBlockObjectReceived(index, total)

image

onFileChunkReceived(file, ofs, size)

image

onGhostAlwaysObjectReceived()

image

onGhostAlwaysStarted(count)

image

FINDING SERVERS

When you offer a game with networked client/server capabilities, there needs to be some means for players to find servers to which to connect. On the Internet, a fairly widely implemented technique is to employ a master server. The master server’s job is generally straightforward and simple. It keeps a list of active game servers and provides a client with the necessary information to connect to any one of the servers if desired.

To see the utility of such a simple system, just take a look at NovaLogic, makers of the successful Delta Force series of first-person shooters. NovaLogic still hosts master servers for customers who bought Delta Force games from the early 2000s! The overhead of such a simple system is minimal, and the benefit in customer goodwill is tremendous.

The Tribes series of games, upon which Torque 3D is based, also offered such master servers, as do many other games out there.

Bohemia Interactive, makers of the popular ARMA series of tactical shooters cum military simulations, uses a mechanism that allows player hosts and customers with dedicated servers to auto-register with GameSpy’s master servers. The client components of their games can then connect to GameSpy’s master server to find missions to dive into. GameSpy provides such capabilities for a dizzying multitude of online games.

On a small- to medium-sized local area network, finding a server to connect to (provided that any servers are actually running) is not too onerous a task. An extremely simple method is to have the client merely examine a specified port on all visible nodes to see if a server is present, and that’s what we’re going to be doing in this chapter.

Code Changes

We are going to implement LAN-based “find a server” support in our version of Emaga for this chapter. We will create Emaga6 by modifying Emaga5, the game from the last chapter.

First, copy your entire EMAGA5 folder to a new folder, called EMAGA6. Then, for the sake of clarity, rename the Emaga5.torsion project file to Emaga6.torsion, and do the same for both the Torsion options and Torsion exports files as well. Now open your new Chapter 6 Torsion project. Choose Project, Properties, and in the Name field, change the name to Ch6 (or a longer variation of your own choosing), and click the Okay button. Then choose File, Save to ensure that the name change sticks. You may not notice the name change right away in the Project panel. If that’s the case, simply quit Torsion, and then re-open your Emaga6.torsion project.

All changes will be made in the control code. In addition to changes to the actual program code, you might want to also change any Chapter 5 comment references so they refer to Chapter 6. For example, the string “Emaga5” shows up in eight files when I do a Search In Files for that string. Some of those references are in echo() statements, so you should probably at the very least change those ones—but it’s your call.

Client—MenuScreen Gui

We’ll make our first change in control/client/interfaces/menuscreen.cs. Open the file and scroll down to bottom. Just above the closing brace and semicolon, add the following code:

new GuiButtonCtrl() {
  profile = "GuiButtonProfile";
  horizSizing = "right";
  vertSizing = "top";
  position = "29 277";
  extent = "110 20";
  minExtent = "8 8";
  visible = "1";
  command = "Canvas.SetContent(ServerScreen);";
  helpTag = "0";
  text = "Connect To Server";
  groupNum = "-1";
  buttonType = "PushButton";
};

The important part here is the command assignment to the button:

Canvas.SetContent(ServerScreen);

This will put up a screen with which we can select a server to join. The code for the screen that is displayed (ServerScreen) is presented later in the chapter. But we’ve a bit more work to do first.

Client—Initialize Module

We’ll make our first change in control/client/initialize.cs. Open that module and locate the function InitializeClient. Add the following statements to the very beginning of the function (after the opening brace):

 $Client::GameTypeQuery = "3D3E";
 $Client::MissionTypeQuery = "Any";

When one of our servers contacts the master server, it uses the variable $Client:: GameTypeQuery to filter out game types that we aren’t interested in. For your game, you can set any game type you like. Here we are going to go with 3D3E because there will be at least one 3D3E server listed on the master server, and for the purpose of illustration it is better to see one or two 3D3E servers listed than nothing at all. You can change this later at your leisure.

The variable $Client::MissionTypeQuery is used to filter whatever specific game play styles are available. By specifying any, we will see any types that are available. This is also something we can define in whatever way we want for our game.

Next, there are a series of calls to Exec. Find the one that loads playerinterface. gui, and put the following line after that one:

  Exec("./interfaces/serverscreen.gui");

Then find the call to Exec that loads screens.cs, and add the following statement after it:

  Exec("./misc/serverscreen.cs");

Toward the end of the function, find the Exec call that loads connections.cs. After that statement, and before the call to Canvas.SetContent, add the following statement:

  SetNetPort(0);

Although we will never use port 0, it is necessary to make this call to ensure that the TCP/IP code in Torque 3D works correctly. Later on in other modules the appropriate port will be set, depending on what we are doing.

Finally, remember earlier where we added some logic to the argument parser for connecting directly to an IP address? Well, now’s the time to add in the code that will make that actually happen. At the very bottom of the function, put the following code:

if ($JoinGameAddress !$= "")
  connect($JoinGameAddress, "", $Pref::Player::Name);
else

directly above the line

Canvas.setContent(SplashScreen);

New Modules

More typing! But not as much as in previous chapters, so don’t fret. We have to add a new interface module and a module to contain the code that manages its behavior.

Client—ServerScreen Interface Module

Now we have to add the ServerScreen interface module. This module defines buttons, text labels, and a scroll control that will appear on the screen; we can use it to query the master server and view the results. Type in the following code, and save it as controlclientinterfacesserverscreen.gui.

//============================================================================
// control/client/interfaces/serverscreen.gui
//
// Server query interface module for 3D3E emaga6 sample game
//
// Copyright (c) 2003, 2006, 2012 by Kenneth C. Finney.
//============================================================================

new GuiChunkedBitmapCtrl(ServerScreen) {
  profile = "GuiContentProfile";
  horizSizing = "width";
  vertSizing = "height";
  position = "0 0";
  extent = "640 480";
  minExtent = "8 8";
  visible = "1";
  bitmap = "./emaga_background";
  useVariable = "0";
  tile = "0";
  helpTag = "0";

  new GuiControl() {
    profile = "GuiWindowProfile";
    horizSizing = "center";
    vertSizing = "center";
    position = "20 90";
    extent = "600 300";
    minExtent = "8 8";
    visible = "1";
    helpTag = "0";

  new GuiTextCtrl() {
    profile = "GuiTextProfile";
    horizSizing = "right";
    vertSizing = "bottom";
    position = "183 5";
    extent = "63 18";
    minExtent = "8 8";
    visible = "1";
    text = "Player Name:";
    maxLength = "255";
    helpTag = "0";
};

new GuiTextEditCtrl() {
  profile = "GuiTextEditProfile";
  horizSizing = "right";
  vertSizing = "bottom";
  position = "250 5";
  extent = "134 18";
  minExtent = "8 8";
  visible = "1";
  variable = "Pref::Player::Name";
  maxLength = "255";
  historySize = "5";
  password = "0";
  tabComplete = "0";
  sinkAllKeyEvents = "0";
    helpTag = "0";
};

new GuiTextCtrl() {
  profile = "GuiTextProfile";
  horizSizing = "right";
  vertSizing = "bottom";
  position = "13 30";
  extent = "24 18";
  minExtent = "8 8";
  visible = "1";
  text = "Private ?";
  maxLength = "255";
  helpTag = "0";
};
new GuiTextCtrl() {
  profile = "GuiTextProfile";
  horizSizing = "right";
  vertSizing = "bottom";
  position = "76 30";
  extent = "63 18";
  minExtent = "8 8";
  visible = "1";
  text = "Server Name";
  maxLength = "255";
  helpTag = "0";
};
new GuiTextCtrl() {
  profile = "GuiTextProfile";
  horizSizing = "right";
  vertSizing = "bottom";
  position = "216 30";
  extent = "20 18";
  minExtent = "8 8";
  visible = "1";
  text = "Ping";
  maxLength = "255";
  helpTag = "0";
};
new GuiTextCtrl() {
  profile = "GuiTextProfile";
  horizSizing = "right";
  vertSizing = "bottom";
  position = "251 30";
  extent = "36 18";
  minExtent = "8 8";
  visible = "1";
  text = "Players";
  maxLength = "255";
  helpTag = "0";
};
new GuiTextCtrl() {
  profile = "GuiTextProfile";
  horizSizing = "right";
  vertSizing = "bottom";
  position = "295 30";
  extent = "38 18";
  minExtent = "8 8";
  visible = "1";
  text = "Version";
  maxLength = "255";
  helpTag = "0";
};
new GuiTextCtrl() {
  profile = "GuiTextProfile";
  horizSizing = "right";
  vertSizing = "bottom";
  position = "433 30";
  extent = "28 18";
  minExtent = "8 8";
  visible = "1";
  text = "Game Description";
  maxLength = "255";
  helpTag = "0";
};

new GuiScrollCtrl() {
  profile = "GuiScrollProfile";
  horizSizing = "right";
  vertSizing = "bottom";
  position = "14 55";
  extent = "580 190";
  minExtent = "8 8";
  visible = "1";
  willFirstRespond = "1";
  hScrollBar = "dynamic";
  vScrollBar = "alwaysOn";
  constantThumbHeight = "0";
  childMargin = "0 0";
  helpTag = "0";
  defaultLineHeight = "15";

  new GuiTextListCtrl(ServerList) {
    profile = "GuiTextArrayProfile";
    horizSizing = "right";
    vertSizing = "bottom";
    position = "2 2";
    extent = "558 48";
    minExtent = "8 8";
    visible = "1";
    enumerate = "0";
    resizeCell = "1";
    columns = "0 30 200 240 280 400";
    fitParentWidth = "1";
    clipColumnText = "0";
    noDuplicates = "false";
    helpTag = "0";
  };
};

new GuiButtonCtrl() {
  profile = "GuiButtonProfile";
  horizSizing = "right";
  vertSizing = "top";
  position = "16 253";
  extent = "127 23";
  minExtent = "8 8";
  visible = "1";
  command = "Canvas.getContent().Close();";
  text = "Close";
  groupNum = "-1";
  buttonType = "PushButton";
  helpTag = "0";
};
new GuiButtonCtrl(JoinServer) {
  profile = "GuiButtonProfile";
  horizSizing = "right";
  vertSizing = "bottom";
  position = "455 253";
  extent = "130 25";
  minExtent = "8 8";
  visible = "1";
  command = "Canvas.getContent().Join();";
  text = "Connect";
  groupNum = "-1";
  buttonType = "PushButton";
  active = "0";
  helpTag = "0";
};

new GuiControl(QueryStatus) {
  profile = "GuiWindowProfile";
  horizSizing = "center";
  vertSizing = "center";
  position = "149 100";
  extent = "310 50";
  minExtent = "8 8";
  visible = "0";
  helpTag = "0";

  new GuiButtonCtrl(CancelQuery) {
     profile = "GuiButtonProfile";
     horizSizing = "right";
     vertSizing = "bottom";
     position = "9 15";
     extent = "64 20";
     minExtent = "8 8";
     visible = "1";
     command = "Canvas.getContent().Cancel();";
     text = "Cancel";
     groupNum = "-1";
     buttonType = "PushButton";
     helpTag = "0";
    };
    new GuiProgressCtrl(StatusBar) {
     profile = "GuiProgressProfile";
     horizSizing = "right";
     vertSizing = "bottom";
     position = "84 15";
     extent = "207 20";
     minExtent = "8 8";
     visible = "1";
     helpTag = "0";
    };
    new GuiTextCtrl(StatusText) {
     profile = "GuiProgressTextProfile";
     horizSizing = "right";
     vertSizing = "bottom";
     position = "85 14";
     extent = "205 20";
     minExtent = "8 8";
     visible = "1";
     maxLength = "255";
     helpTag = "0";
    };
  };
 };
};//============================================================================
// control/client/interfaces/serverscreen.gui
//
// Server query interface module for 3D3E Emaga6 sample game
//

The first half of the module is an interface definition, defining a number of buttons, text labels, and a scroll control that will appear on the screen. Most of the properties and control types have been covered in previous chapters; however, some of them are of particular note here.

Table 6.3 Selected GuiScrollCtrl Properties

image

The first item of interest is the GuiScrollCtrl. This control provides a scrollable vertical list of records; in this case, it will be a list of servers that satisfy the filters used in subsequent Query calls that we will look at a bit later.

Some of the GuiScrollCtrl properties of interest are explained in Table 6.3.

The next significant control to examine is the GuiTextEditCtrl. It has an interesting property, shown by this statement:

  variable = "Pref::Player::Name";

What this does is display the contents of the variable Pref::Player::Name in the control’s content. If we change that content by placing our edit cursor in the control’s field while it is being displayed and typing in new text, then the contents of the variable Pref::Player::Name are also changed.

Also in this GuiTextEditCtrl control is the following statement:

  historySize = "5";

This control has the ability to store a history of previous values that were held in the control’s edit box. We can scroll through the list’s previous values by pressing the Up Arrow and Down Arrow keys. This property sets the maximum number of values that can be saved in the control’s history. A setting of 0 means that no history will be saved.

Now go take a look at the control of type GuiControl with the name QueryStatus. This is the definition of a subscreen that will display the progress of the query. It contains a couple of other controls that we’ve seen before, but I just want you to note how they are nested within this control, which is nested within the larger ServerScreen.

Client—ServerScreen Code Module

Next, we will add the ServerScreen code module. This module defines how the ServerScreen interface module will behave. Type in the following code, and save it as controlclientmiscserverscreen.cs.

//============================================================================
// control/client/misc/serverscreen.cs
//
// Server query code module for 3D3e Emaga6 sample game
//
// Copyright (c) 2003, 2006, 2012 by Kenneth C. Finney.
//============================================================================
function ServerScreen::onWake()
{
  JoinServer.SetActive(ServerList.rowCount() > 0);
  ServerScreen.queryLan();
}


function ServerScreen::QueryLan(%this)
{
  QueryLANServers(
    28000,      // lanPort for local queries
    0,        // Query flags
    $Client::GameTypeQuery,      // gameTypes
    $Client::MissionTypeQuery,   // missionType
    0,        // minPlayers
    100,      // maxPlayers
    0,        // maxBots
    2,        // regionMask
    0,        // maxPing
    100,      // minCPU
    0         // filterFlags
    );
}

function ServerScreen::Cancel(%this)
{
   CancelServerQuery();
}

function ServerScreen::Close(%this)
{
   CancelServerQuery();
   Canvas.SetContent(MenuScreen);
}

function ServerScreen::Update(%this)
{
   QueryStatus.SetVisible(false);
   ServerList.Clear();
   %sc = GetServerCount();
   for (%i = 0; %i > %sc; %i++)
   {
     SetServerInfo(%i);
     ServerList.AddRow(%i,
       ($ServerInfo::Password? "Yes": "No") TAB
       $ServerInfo::Name TAB
       $ServerInfo::Ping TAB
       $ServerInfo::PlayerCount @ "/" @ $ServerInfo::MaxPlayers TAB
       $ServerInfo::Version TAB
       $ServerInfo::GameType TAB
       %i);
     }
     ServerList.Sort(0);
     ServerList.SetSelectedRow(0);
     ServerList.ScrollVisible(0);
     JoinServer.SetActive(ServerList.RowCount() > 0);
}

function ServerScreen::Join(%this)
{
   CancelServerQuery();
   %id = ServerList.GetSelectedId();
   %index = GetField(ServerList.GetRowTextById(%id),6);
   if (SetServerInfo(%index)) {
   %conn = new GameConnection(ServerConnection);
   %conn.SetConnectArgs($pref::Player::Name);
   %conn.SetJoinPassword($Client::Password);
   %conn.Connect($ServerInfo::Address);
   }
}

function onServerQueryStatus(%status, %msg, %value)
{
  if (!QueryStatus.IsVisible())
     QueryStatus.SetVisible(true);

  switch$ (%status) {
     case "start":

     case "ping":
      StatusText.SetText("Ping Servers");
      StatusBar.SetValue(%value);

     case "query":

     case "done":
      QueryStatus.SetVisible(false);
      ServerScreen.Update();
   }
}

This module is where we’ve put the code that controls how the Master Server screen behaves.

The first function, ServerScreen::onWake, defines what to do when the screen is displayed. In this case we first set the Join button to be active if there are any servers in the server list at the moment we display the screen. Then ServerScreen::queryLAN is called. It executes a call to QueryLANServers, which reaches out across the local area network and talks to each computer on port 28000 (you can use any available port). If it manages to contact a computer with a game server running on that port, it establishes contact with the game server, obtains some information from it, and adds that server to a list. There are quite a few parameters to the call to QueryLANServers. The following syntax definition shows them in more detail:

QueryLANServers (port,flags,gtype,mtype,minplayers,maxplayers,maxbots,region, ping,cpu,filters,buddycount, buddylist)

image

The response to the QueryLANServers function is accessible from the ServerList array.

The next function, ServerScreen::Cancel, is called when the Cancel button is clicked while the query is under way.

After that is the ServerScreen::Close function, which is called when the user clicks the Close button. It cancels any pending query and then returns to the MenuScreen.

ServerScreen::Update is the function that inserts the obtained information in the ServerList after it is obtained from the master server. The information is found in the $ServerInfo array. To update the scrolling display, we find the number of servers that pass the filters on the master by calling GetServerCount. Then we iterate through our displayable list, extracting the fields from each $ServerInfo record. Take note of the call to SetServerInfo. Passing an index number to this function sets the $ServerInfo array to point to a specific record in the MasterServerList. Then we access the individual fields in the $ServerInfo array by referencing them with the colon operator: $ServerInfo::PlayerCount or $ServerInfo::Name, to demonstrate with two examples.

The next function, ServerScreen::Join, defines how we go about joining a server that has been selected from the list. First, we cancel any outstanding queries, get the handle of the server record that is highlighted in the interface, and then use that to obtain the index number of the server record. We use SetServerInfo to set the $ServerInfo array to point to the right server record, and then we can access the values. After setting some network parameters, we finally use $ServerInfo:: Address to make the network connection.

The last function in the module is the message handler callback that makes the whole shebang go: onServerQueryStatus. It gets called repeatedly as the server query process unfolds. We use the %status variable to determine what response we are receiving from the master server, and then we use either the %msg or %value variable, set by the master server to update various fields in the displayed server list. The start and query cases aren’t needed in our example.

DEDICATED SERVER

Sometimes we will want to host a game as a server without having to bother with a graphical user interface. One reason we might want to do this is because we want to run the server on a computer that doesn’t have a 3D accelerated graphics adapter. Another reason is because we might want to test our client/server connectivity and master server query capabilities. This need arises because we can’t run two instances of the Torque 3D graphical client at the same time. However, if we have the ability to run as a dedicated server, we can run multiple dedicated servers, while running one instance of the graphical client, all on the same computer. And if we have set up the dedicated servers appropriately, other players out on the network can connect to our servers.

There are a few more modules you will have to change to implement the dedicated server capabilities.

Root Main Module

In this module we’ll need to add some command line switches in case we want to use the command line interface of Windows, or we’ll need to decide to embed the switches in a Windows shortcut. Either of these methods is how we can tell the game to run the server in dedicated mode.

The knowledge that the game will be running as a dedicated server is stored in the global variable $Server::Dedicated. Our first change will be to modify whether or not to initialize the graphics rendering based upon the value of $Server::Dedicated.

In the module main.cs located in the root game folder (which is the folder where the T3Ddemo.exe executable is located for your Chapter 6 version of Emaga), locate the

CreateCanvas function, then type this code:
  if ($Server::Dedicated)
  {
  GFXInit::createNullDevice();
  return true;
  }

Immediately after the opening brace of the function.

This code tests the $Server::Dedicated variable, and if it is set to true, then it calls a special version of the graphics initialization code to utilize the null device, which means, well, no graphics will be used.

Next, in the same file, locate the ParseArgs function, and scroll down until you find the statement containing switch$(%currentarg). Type the following code in directly after the $switch statement (after the opening brace):

case "-dedicated":
  $Server::Dedicated = true;
  EnableWinConsole(true);
  $argumentFlag[%i]++;

case "-map":
  $argumentFlag[%i]++;
  if (%nextArgExists)
  {
      $mapArgument = %nextArgument;
      $argumentFlag[%i+1]++;
      %i++;
  }
  else
      Error("Error: Missing argument. Usage: -map <filename>");
case "-connect":
    $argumentFlag [%i]++;
    if (%hasNextArg) {
     $JoinGameAddress = %nextArg;
     $argumentFlag [%i+1]++;
     %i++;
    }
    else
     error("Error: Missing argument. Usage: -connect <ip_address>");

The first two switches are needed to run a dedicated server. The -dedicated switch puts us into the right mode, and then the -map switch tells us which mission map to load when the server first starts running.

The third switch, -connect, allows us to connect to a server by specifying the IP address in the command line, as an argument to this switch. This isn’t really a dedicated server option, but we might as well add it in here now, and have it available to use if we feel like it later.

The result of these changes is that we can now invoke the dedicated server mode by launching the game with the following syntax from the command line (don’t try it yet): T3Ddemo.exe -dedicated -map assets/maps/book_ch6.mis.

The game will launch, and all you will see will be a console window. You will be able to type in console script statements, just as you can when you use the Tilde (“~”) key in the graphical client interface. However, don’t try this just yet, because we still need to add the actual dedicated server code!

You can also create a shortcut to the T3Ddemo.exe executable and modify the Target box in the shortcut properties to match the command line syntax above. Then you can launch the server merely by double-clicking on the shortcut icon.

Control—Main Module

Next, we have a couple of quick modifications to make to control/main.cs.

At the very top of the file, put the following line of code immediately after the block of text that contains the copyright information:

loadDir("core");

If your version doesn’t have copyright information at the top, don’t sweat it. Just put that line at the very top of the file, and carry on. What this line does is tell Torque to add the core folder into its list of Mods, so that its code will be taken into account when T3D tries to find the functions that you tell it to execute. There is some predefined code in the core directory that’s very handy to have around when we want it. In the OnStart function, locate the line that contains InitializeClient. Replace that one line with these four lines:

if ($Server::Dedicated)
   InitializeDedicatedServer();
else
   InitializeClient();

Now, when the program detects that the -dedicated switch was used, as described in the previous section, it will fire up in dedicated mode, not in client mode.

Also, when the server needs to be stopped, we have to “destroy” it, in anticipation of running it again, but it may have different settings next time it is run, so we can’t leave it hanging around moping for players to come along. Also, if we don’t do this, T3D might crash—so there’s that, too.

Find the OnExit function, and above the line that reads:

Parent::onExit();

put this little block of code:

if ($Server::Dedicated)
   destroyServer();
else
   disconnect();

Control—Server Initialize Module

Okay, the meat of the dedicated server code is contained in this module. Open up the module control/server/initialize.cs, and type in the following lines just before the InitBaseServer function.

$pref::Master0 = "2:master.garagegames.com:28002";

$Pref::Server::ConnectionError = "You do not have the correct version of 3D3E client or
the related art needed to play on this server. This is the server for Chapter 6. Please
check that chapter for directions.";

$Pref::Server::FloodProtectionEnabled = 1;
$Pref::Server::Info = "3D Game Programming All-In-One by Kenneth C. Finney.";
$Pref::Server::MaxPlayers = 64;
$Pref::Server::Name = "3D3E Book - Chapter 6 Server";
$Pref::Server::Password = "";
$Pref::Server::Port = 28000;
$Pref::Server::RegionMask = 2;
$Pref::Server::TimeLimit = 20;
$Pref::Net::LagThreshold = "400";
$pref::Net::PacketRateToClient = "10";
$pref::Net::PacketRateToServer = "32";
$pref::Net::PacketSize = "200";
$pref::Net::Port = 28000;

You can change the string values to be anything you like as long as it suits your purposes. You should leave the RegionMask as is for now.

Next, locate the function InitializeServer again, and insert the following lines at the very beginning of the function:

$Server::GameType = "3D3E";
$Server::MissionType = "Emaga6";
$Server::Status = "Unknown";

The value of $Server::Status will be updated when the server makes contact with the master server.

Finally, you will need to add this entire function to the end of the module:

function InitializeDedicatedServer()
{
  EnableWinConsole(true);
  Echo("
--------- Starting Dedicated Server ---------");

  $Server::Dedicated = true;

  if ($mapArgument !$= "") {
   CreateServer("MultiPlayer", $mapArgument);
  }
  else
   Echo("No map specified (use -map <filename>)");
}

This function enables the Windows console, sets the dedicated flag, and then calls CreateServer with the appropriate values. Now it may not do very much and therefore seem to be not too necessary, but the significance with the Initialize-DedicatedServer function is in what it doesn’t do compared with the InitializeClient function, which would have otherwise been called. So that’s the reason why it exists.

TESTING EMAGA6

With all the changes we’ve made here, we’re going to want to see Emaga6 run. It’s really fairly easy. Open a command shell in Windows by clicking the Start button, and typing cmd into the Run box, and then change to the folder where you’ve built the code for this chapter’s program (EMAGA6) by typing cd EMAGA6 .

You need to ensure that the command prompt is pointing you to the correct hard drive. If you have created your Emaga6 game on the E drive, then your command prompt should read as E:. If it doesn’t match, then type in the correct drive letter, followed by a colon. For example, to change to drive E from some other drive, just type in E: followed by pressing the Return key.

After you’ve wrestled the command prompt into submission, run the dedicated server by typing in this command: T3Ddemo.exe -dedicated -map assets/maps/book_ch6.mis.

Note


When you are testing, if you should happen to peek into the console while the game is running or into the console log file afterward, you might notice a whole bunch of lines like this:

   No such file 'assets/models/avatars/male/player.jpg'.

Don’t worry—that’s not even an error. When loading shapes, Torque 3D has an automatic system that looks for texture files associated with those shapes. Torque 3D supports both JPG and PNG image file types for use with shapes, and depending on how the textures are defined in the shape files themselves, Torque 3D may not find the specified file immediately. When that is the case, it goes through its seek routine and spews out a message every time one of its attempts doesn’t succeed. Once the correct file is located, Torque 3D moves on to the next instruction without any further fuss over that particular shape.

And if you don’t see that message, don’t worry about it.


After it displays lots of start-up information, it will eventually settle down and tell you in the console window that it has successfully loaded a mission. When you see these things, your dedicated server is running fine.

Next, double-click your T3Ddemo.exe icon as you’ve done in the past to run an Emaga client. When the Menus screen appears, click the Connect To Server button. Look for the 3DGAPI1 server name (or whatever value you assigned to $Pref:: Server::Name in the Control—Initialize module). Select that server entry, and then click Join. Watch the progress bars, and eventually you will find yourself deposited in the game. Send copies of this to your friends, and get them to join in for some freewheeling havoc or reckless mayhem—whichever you prefer!

Testing Direct Messaging

If you will recall, back at the beginning of the chapter, in the “Direct Messaging” section, we discussed the functions CommandToServer and CommandToClient. You might want to take this opportunity to test the code shown in that section.

Put the ServerCmdTellEveryone and TellAll functions at the top of your EMAGA6controlserverserver.cs module (above OnServerCreated), and then add the SendMacro function to the end of your EMAGA6controlclientmisc presetkeys.cs module. Also in the presetkeys.cs module, add the following after the SendMacro function that you just added:

function clientCmdTellMessage(%sender, %msgString)
{
  MessagePopup( "HELLO EVERYBODY", %msgString, 1000);
}
PlayerKeymap.bindCmd(keyboard, "1", "SendMacro(1);", "");
PlayerKeymap.bindCmd(keyboard, "2", "SendMacro(2);", "");
PlayerKeymap.bindCmd(keyboard, "3", "SendMacro(3);", "");

You can go ahead and test it when you’ve completed those additions, if you like. You can test it both in stand-alone (player-hosted) form or using a dedicated server with a client on the same or different machine on a LAN.

MOVING RIGHT ALONG

Now you have some understanding of how to pass messages back and forth between the client and the server. Keep in mind when you contemplate these things that there can be many clients—hockey socks full of clients, even. There will probably only be one server, but you are in no way restricted to only one server. It’s all a matter of programming.

You’ve also seen how you can track specific clients on the server via their GameConnections. As long as you know the handle of the client, you can access any of that client’s data.

In the next chapter, we’ll poke our noses into the common code that we have been shying away from. We want to do this so that we can get a better big-picture understanding of how our game can operate.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.30.210