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 fairly 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 the original Delta Force games from the late 1990s! The overhead of such a simple system is minimal, and the benefit in customer good will is tremendous.

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

On a small- to medium-sized local area network, this 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 "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 C:\Emaga5 folder to a new folder, called C:\Emaga6. Then, for the sake of clarity, rename the UltraEdit project file to chapter6.prj. Now open your new Chapter 6 UltraEdit 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—it's your call.

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:

   $Client::GameTypeQuery = "3DGPAI1";   $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 3DGPAI1 because there will be at least one 3DGPAI1 server listed on the master server, and for the purpose of illustration it is better to see one or two 3DGPAI1servers 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.

Farther down will be a call to InitCanvas. Although it is not really important to make the master server stuff work, change that statement to this:

   InitCanvas("emaga6 - 3DGPAi1 Sample Game"); 

Doing so reflects the fact that we are now in Chapter 6 and not in Chapter 5 anymore.

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"); 

Finally, 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); 

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

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 control/client/interfaces/serverscreen.gui.

 //============================================================================ // control/client/interfaces/serverscreen.gui // //  Server query interface module for 3DGPAI1 emaga6 sample game // //  Copyright (c) 2003 by Kenneth C. Finney. //============================================================================ new GuiChunkedBitmapCtrl(ServerScreen) {    profile = "GuiContentProfile";    horizSizing = "width";    vertSizing = "height";    position = "0 0";    extent = "640 480";    minExtent = "8 8";    visible = "1";    helpTag = "0";    bitmap = "./emaga_background";    useVariable = "0";    tile = "0";    new GuiControl() {       profile = "GuiWindowProfile";       horizSizing = "center";       vertSizing = "center";       position = "100 100";       extent = "600 300";       minExtent = "8 8";       visible = "1";       helpTag = "0";       new GuiTextCtrl() {          profile = "GuiTextProfile";          horizSizing = "right";          vertSizing = "bottom";          position = "15 40";          extent = "30 20";          minExtent = "8 8";          visible = "1";          helpTag = "0";          text = "Pass";          maxLength = "255";       };       new GuiButtonCtrl(JoinServer) {          profile = "GuiButtonProfile";          horizSizing = "right";          vertSizing = "bottom";          position = "318 272";          extent = "127 23";          minExtent = "8 8";          visible = "1";          command = "Canvas.getContent().Join();";          helpTag = "0";          text = "Connect";             active = "0";       };       new GuiScrollCtrl() {          profile = "GuiScrollProfile";          horizSizing = "right";          vertSizing = "bottom";          position = "10 75";          extent = "437 186";          minExtent = "8 8";          visible = "1";          helpTag = "0";          willFirstRespond = "1";          hScrollBar = "dynamic";          vScrollBar = "alwaysOn";          constantThumbHeight = "0";          defaultLineHeight = "15";          childMargin = "0 0";          new GuiTextListCtrl(ServerList) {             profile = "GuiTextArrayProfile";             horizSizing = "right";             vertSizing = "bottom";             position = "0 0";             extent = "419 8";             minExtent = "8 8";             visible = "1";             helpTag = "0";             enumerate = "0";             resizeCell = "1";             columns = "0 40 195 260 325 385";             fitParentWidth = "1";             clipColumnText = "0";                noDuplicates = "false";          };       };       new GuiTextEditCtrl() {          profile = "GuiTextEditProfile";          horizSizing = "right";          vertSizing = "bottom";          position = "98 15";          extent = "134 16";          minExtent = "8 8";          visible = "1";          variable = "Pref::Player::Name";          helpTag = "0";          maxLength = "255";          historySize = "0";          password = "0";          tabComplete = "0";       };       new GuiTextCtrl() {          profile = "GuiTextProfile";          horizSizing = "right";          vertSizing = "bottom";          position = "12 11";          extent = "79 20";          minExtent = "8 8";          visible = "1";          helpTag = "0";          text = "Player Name:";          maxLength = "255";       };       new GuiTextCtrl() {          profile = "GuiTextProfile";          horizSizing = "right";          vertSizing = "bottom";          position = "269 42";          extent = "44 20";          minExtent = "8 8";          visible = "1";          helpTag = "0";          text = "Players";          maxLength = "255";       };       new GuiTextCtrl() {          profile = "GuiTextProfile";          horizSizing = "right";          vertSizing = "bottom";          position = "335 42";          extent = "44 20";          minExtent = "8 8";          visible = "1";          helpTag = "0";          text = "Version";          maxLength = "255";       };       new GuiTextCtrl() {          profile = "GuiTextProfile";          horizSizing = "right";          vertSizing = "bottom";          position = "412 42";          extent = "35 20";          minExtent = "8 8";          visible = "1";          helpTag = "0";          text = "Game";          maxLength = "255";       };       new GuiTextCtrl() {          profile = "GuiTextProfile";          horizSizing = "right";          vertSizing = "bottom";          position = "212 42";          extent = "26 20";          minExtent = "8 8";          visible = "1";          helpTag = "0";          text = "Ping";          maxLength = "255";       };       new GuiTextCtrl() {          profile = "GuiTextProfile";          horizSizing = "right";          vertSizing = "bottom";          position = "72 42";          extent = "74 20";          minExtent = "8 8";          visible = "1";          helpTag = "0";          text = "Server";          maxLength = "255";       };       new GuiButtonCtrl() {          profile = "GuiButtonProfile";          horizSizing = "right";          vertSizing = "top";          position = "10 272";          extent = "127 23";          minExtent = "8 8";          visible = "1";          command = "Canvas.getContent().Close();";          helpTag = "0";          text = "Close";       };       new GuiControl(QueryStatus) {          profile = "GuiWindowProfile";          horizSizing = "center";          vertSizing = "center";          position = "72 129";          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();";             helpTag = "0";             text = "Cancel Query";          };          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";             helpTag = "0";             maxLength = "255";          };       };    }; }; 

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.

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.

Table 6.3: Selected GuiScrollCtrl Properties

Property

Description

willFirstRespond

If set to true or 1, indicates that this control will respond to user inputs first, before passing them on to other controls.

hScrollBar

Indicates how to decide whether to display the horizontal scroll bar. The choices are:
alwaysOn: The scroll bar is always visible.
alwaysOff: The scroll bar is never visible.
dynamic: The scroll bar is not visible until the number of records in the list exceeds the number of lines available to display them. If this happens the scroll bar is turned on and made visible.

vScrollBar

The same as hScrollBar but applies to the vertical scroll bar.

constantThumbHeight

Indicates whether the thumb, the small rectangular widget in the scroll bar that moves as you scroll, will have a size that is proportional to the number of entries in the list (the longer the list, the smaller the thumb) or will have a constant size. Setting this property to 1 ensures a constant size; 0 ensures proportional sizing.

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 = "0"; 

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 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 control/client/misc/serverscreen.cs.

 //============================================================================ // control/client/misc/serverscreen.cs // // Server query code module for 3DGPAI1 emaga6 sample game // // Copyright (c) 2003 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 MasterScreen::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);       Screen.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, MasterScreen::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)

Parameters:

port

The TCP/IP port where game servers are expected to be found.

flags

Query flags. Choices:
0 00 = online query
0 01 = offline query
0 02 = no string compression

gtype

Game type string

mtype

Mission type string

minplayers

Minimum number of players for viable game

maxplayers

Maximum allowable players

maxbots

Maximum allowable connected AI bots

region

Numeric discriminating mask

ping

Maximum ping for connecting clients; 0 means no maximum

mincpu

Minimum specified CPU capability

filterflags

Server filters. Choices:
0 00 = dedicated
0 01 = not password protected
0 02 = Linux
0 80 = current version

buddycount

Number of buddy servers in buddy list

buddylist

List of server names that are buddies to this server

Return:

nothing

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

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

After that is the ServerScreen::Close function, which is called when the user presses 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::Name 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 the 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 she-bang 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.




3D Game Programming All in One
3D Game Programming All in One (Course Technology PTR Game Development Series)
ISBN: 159200136X
EAN: 2147483647
Year: 2006
Pages: 197

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net