Selected Common Server Modules


Next, we will take a close look at some of the common code server modules. The modules selected are the ones that will best help illuminate how Torque operates.

The Server Module

InitBaseServer loads the common server module, server.cs. When we examine this module we see the following functions:

    PortInit    CreateServer    DestroyServer    ResetServerDefaults    AddToServerGuidList    RemoveFromServerGuidList    OnServerInfoQuery 

It's not hard to get the sense from that list that this is a pretty critical module!

PortInit tries to seize control of the assigned TCP/IP port, and if it can't, it starts incrementing the port number until it finds an open one it can use.

CreateServer does the obvious, but it also does some interesting things along the way. First, it makes a call to DestroyServer! This is not as wacky as it might seem; while DestroyServer does release and disable resources, it does so only after making sure the resources exist. So there's no danger of referencing something that doesn't exist, which would thus cause a crash. You need to specify the server type (single- [default] or multi-player) and the mission name. The PortInit function is called from here, if the server will be a multiplayer server. The last, but certainly not the least, thing that CreateServer does is call LoadMission. This call kicks off a long and somewhat involved chain of events that we will cover in a later section.

DestroyServer releases and disables resources, as mentioned, and also game mechanisms. It stops further connections from happening and deletes any existing ones; turns off the heartbeat processing; deletes all of the server objects in MissionGroup, MissionCleanup, and ServerGroup; and finally, purges all datablocks from memory.

ResetServerDefaults is merely a convenient mechanism for reloading the files in which the server default variable initializations are stored.

AddToServerGuidList and RemoveFromServerGuidList are two functions for managing the list of clients that are connected to the server.

OnServerInfoQuery is a message handler for handling queries from a master server. It merely returns the string "Doing OK". The master server, if there is one, will see this and know that the server is alive. It could say anything—there could even be just a single-space character in the string. The important point is that if the server is not doing okay, then the function will not even be called, so the master server would never see the message, would time out, and then would take appropriate action (such as panicking or something useful like that).

The Message Module

InitBaseServer loads the common server-side message module, message.cs. Most of this module is dedicated to providing in-game chat capabilities for players.

    MessageClient    MessageTeam    MessageTeamExcept    MessageAll    MessageAllExcept    ChatMessageClient    ChatMessageTeam    ChatMessageAll    SpamAlert    GameConnection::SpamMessageTimeout    GameConnection::SpamReset 

The first five functions in the preceding list are for sending server-type messages to individual clients, all clients on a team, and all clients in a game. There are also exception messages where everyone is sent the message except a specified client.

Next are the three chat message functions. These are linked to the chat interfaces that players will use to communicate with each other.

These functions all use the CommandToServer (see Chapter 6) function internally. It is important to note that there will need to be message handlers for these functions on the client side.

The three spam control functions are used in conjunction with the chat message functions. SpamAlert is called just before each outgoing chat message is processed for sending. Its purpose is to detect if a player is swamping the chat window with messages—this action is called spamming the chat window. If there are too many messages in a short time frame as determined by the SpamMessageTimeout method, then the offending message is suppressed, and an alert message is sent to the client saying something like this: "Enough already! Take a break." Well, you could say it more diplomatically than that, but you get the idea. SpamReset merely sets the client's spam state back to normal after an appropriately silent interval.

The MissionLoad Module

Torque has a concept of mission that corresponds to what many other games, especially those of the first-person shooter genre, call maps. A mission is defined in a mission file that has the extension of .mis. Mission files contain the information that specifies objects in the game world, as well as their placement in the world. Everything that appears in the game world is defined there: items, players, spawn points, triggers, water definitions, sky definitions, and so on.

Missions are downloaded from the server to the client at mission start time or when a client joins a mission already in progress. In this way the server has total control over what the client sees and experiences in the mission.

Here are the contents of the common/server/missionload.cs module.

 //----------------------------------------------------------------------------- // Torque Game Engine // // Copyright (c) 2001 GarageGames.com // Portions Copyright (c) 2001 by Sierra Online, Inc. //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Server mission loading //----------------------------------------------------------------------------- // On every mission load except the first, there is a pause after // the initial mission info is downloaded to the client. $MissionLoadPause = 5000; function LoadMission( %missionName, %isFirstMission ) {    EndMission();    Echo("*** LOADING MISSION: " @ %missionName);    Echo("*** Stage 1 load");    // Reset all of these    ClearCenterPrintAll();    ClearBottomPrintAll();    // increment the mission sequence (used for ghost sequencing)    $missionSequence++;    $missionRunning = false;    $Server::MissionFile = %missionName;    // Extract mission info from the mission file,    // including the display name and stuff to send    // to the client.    BuildLoadInfo( %missionName );    // Download mission info to the clients    %count = ClientGroup.GetCount();    for( %cl = 0; %cl < %count; %cl++ ) {       %client = ClientGroup.GetObject( %cl );       if (!%client.IsAIControlled())          SendLoadInfoToClient(%client);    }    // if this isn't the first mission, allow some time for the server    // to transmit information to the clients:    if( %isFirstMission || $Server::ServerType $= "SinglePlayer" )       LoadMissionStage2();    else       schedule( $MissionLoadPause, ServerGroup, LoadMissionStage2 ); } function LoadMissionStage2() {    // Create the mission group off the ServerGroup    Echo("*** Stage 2 load");    $instantGroup = ServerGroup;    // Make sure the mission exists    %file = $Server::MissionFile;    if( !IsFile( %file ) ) {       Error( "Could not find mission " @ %file );       return;    }    // Calculate the mission CRC. The CRC is used by the clients    // to cache mission lighting.    $missionCRC = GetFileCRC( %file );    // Exec the mission, objects are added to the ServerGroup    Exec(%file);    // If there was a problem with the load, let's try another mission    if( !IsObject(MissionGroup) ) {       Error( "No 'MissionGroup' found in mission \"" @ $missionName @ "\"." );       schedule( 3000, ServerGroup, CycleMissions );       return;    }    // Mission cleanup group    new SimGroup( MissionCleanup );    $instantGroup = MissionCleanup;    // Construct MOD paths    PathOnMissionLoadDone();    // Mission loading done...    Echo("*** Mission loaded");    // Start all the clients in the mission    $missionRunning = true;    for( %clientIndex = 0; %clientIndex < ClientGroup.GetCount(); %clientIndex++ )       ClientGroup.GetObject(%clientIndex).LoadMission();    // Go ahead and launch the game    OnMissionLoaded();    PurgeResources(); } function EndMission() {    if (!IsObject( MissionGroup ))       return;    Echo("*** ENDING MISSION");    // Inform the game code we're done.    OnMissionEnded();    // Inform the clients    for( %clientIndex = 0; %clientIndex < ClientGroup.GetCount(); %clientIndex++ ) {       // clear ghosts and paths from all clients       %cl = ClientGroup.GetObject( %clientIndex );       %cl.EndMission();       %cl.ResetGhosting();       %cl.ClearPaths();    }    // Delete everything    MissionGroup.Delete();    MissionCleanup.Delete();    $ServerGroup.Delete();    $ServerGroup = new SimGroup(ServerGroup); } function ResetMission() {    Echo("*** MISSION RESET");    // Remove any temporary mission objects    MissionCleanup.Delete();    $instantGroup = ServerGroup;    new SimGroup( MissionCleanup );    $instantGroup = MissionCleanup;    //    OnMissionReset(); } 

Here are the mission loading–oriented functions on the server contained in this module:

 LoadMission LoadMissionStage2 EndMission ResetMission 

LoadMission, as we saw in an earlier section, is called in the CreateServer function. It kicks off the process of loading a mission onto the server. Mission information is assembled from the mission file and sent to all the clients for display to their users.

After the mission file loads, LoadMissionStage2 is called. In this function, the server calculates the CRC value for the mission and saves it for later use.

Once the mission is successfully loaded onto the server, each client is sent the mission via a call to its GameConnection object's LoadMission method.

start sidebar
What's a CRC Value, and Why Should I Care?

We use a Cyclic Redundancy Check (CRC) when transmitting data over potentially error-prone media. Networking protocols use CRCs at a low level to verify that the sent data is the same data that was received.

A CRC is a mathematical computation performed on data that arrives at a number that represents both the content of the data and how it's arranged. The point is that the number, called a checksum, uniquely identifies the set of data, like a fingerprint.

By comparing the checksum of a set of data to another data set's checksum, you can decide if the two data sets are identical.

Why should you care? Well, in addition to the simple goal of maintaining data integrity, CRCs are another arrow in your anticheat quiver. You can use CRCs to ensure that files stored on the clients are the same as the files on the server and, in this regard, that all the clients have the same files— the result is that the playing field is level.

end sidebar

EndMission releases resources and disables other mission-related mechanisms, clearing the server to load a new mission when tasked to do so.

ResetMission can be called from the EndGame function in the control/server/misc/game.cs module to prepare the server for a new mission if you are using mission cycling techniques.

The MissionDownload Module

Here are the contents of the common/server/missiondownload.cs module.

 //----------------------------------------------------------------------------- // Torque Game Engine // // Copyright (c) 2001 GarageGames.com // Portions Copyright (c) 2001 by Sierra Online, Inc. //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Mission Loading // The server portion of the client/server mission loading process //----------------------------------------------------------------------------- function GameConnection::LoadMission(%this) {    // Send over the information that will display the server info.    // When we learn it got there, we'll send the datablocks.    %this.currentPhase = 0;    if (%this.IsAIControlled())    {       // Cut to the chase...       %this.OnClientEnterGame();    }    else    {       CommandToClient(%this, 'MissionStartPhase1', $missionSequence,          $Server::MissionFile, MissionGroup.musicTrack);       Echo("*** Sending mission load to client: " @ $Server::MissionFile);    } } function ServerCmdMissionStartPhase1Ack(%client, %seq) {    // Make sure to ignore calls from a previous mission load    if (%seq != $missionSequence || !$MissionRunning)       return;    if (%client.currentPhase != 0)       return;    %client.currentPhase = 1;    // Start with the CRC    %client.SetMissionCRC( $missionCRC );    // Send over the datablocks...    // OnDataBlocksDone will get called when have confirmation    // that they've all been received.    %client.TransmitDataBlocks($missionSequence); } function GameConnection::OnDataBlocksDone( %this, %missionSequence ) {    // Make sure to ignore calls from a previous mission load    if (%missionSequence != $missionSequence)       return;    if (%this.currentPhase != 1)       return;    %this.currentPhase = 1.5;    // On to the next phase    CommandToClient(%this, 'MissionStartPhase2', $missionSequence, $Server::MissionFile); } function ServerCmdMissionStartPhase2Ack(%client, %seq) {    // Make sure to ignore calls from a previous mission load    if (%seq != $missionSequence || !$MissionRunning)       return;    if (%client.currentPhase != 1.5)       return;    %client.currentPhase = 2;    // Update mod paths, this needs to get there before the objects.    %client.TransmitPaths();    // Start ghosting objects to the client    %client.ActivateGhosting(); } function GameConnection::ClientWantsGhostAlwaysRetry(%client) {    if($MissionRunning)       %client.ActivateGhosting(); } function GameConnection::OnGhostAlwaysFailed(%client) { } function GameConnection::OnGhostAlwaysObjectsReceived(%client) {    // Ready for next phase.    CommandToClient(%client, 'MissionStartPhase3', $missionSequence, $Server::Mission- File); } function ServerCmdMissionStartPhase3Ack(%client, %seq) {    // Make sure to ignore calls from a previous mission load    if(%seq != $missionSequence || !$MissionRunning)       return;    if(%client.currentPhase != 2)       return;    %client.currentPhase = 3;    // Server is ready to drop into the game    %client.StartMission();    %client.OnClientEnterGame(); } 

The following functions and GameConnection methods are defined in the MissionDownload module:

    GameConnection::LoadMission    GameConnection::OnDataBlocksDone    GameConnection::ClientWantsGhostAlwaysRetry    GameConnection::OnGhostAlwaysFailed    GameConnection::OnGhostAlwaysObjectsReceived    ServerCmdMissionStartPhase1Ack    ServerCmdMissionStartPhase2Ack    ServerCmdMissionStartPhase3Ack 

This module handles the server-side activities in the mission download process (see Figure 7.1).

click to expand
Figure 7.1: Mission download phases.

This module contains the mission download methods for each client's GameConnection object.

The download process for the client object starts when its LoadMission method in this module is called at the end of the server's LoadMissionStage2 function in the server's MissionLoad module described in the previous section. It then embarks on a phased series of activities coordinated between the client server (see Figure 7.2). The messaging system for this process is the CommandToServer and CommandToClient pair of direct messaging functions.

click to expand
Figure 7.2: Mission download process.

The server invokes the client MissionStartPhasen (where n is 1, 2, or 3) function to request permission to start each phase. This is done using our old friend CommandToServer. When a client is ready for a phase, it responds with a MissionStartPhasenAck message, for which there is a handler on the server contained in this module.

The handler GameConnection::onDataBlocksDone is invoked when phase one has finished. This handler then initiates phase two by sending the MissionStartPhase2 message to the client.

The GameConnection::onGhostAlwaysObjects Received handler is invoked when phase two is completed. At the end of this phase, the client has all of the data needed to replicate the server's version of any dynamic objects in the game that are ghosted to the clients. This handler then sends the MissionStartPhase3 message to the client.

When the server receives the MissionStart-Phase3Ack message, it then starts the mission for each client, inserting the client into the game.

The ClientConnection Module

The ClientConnection module is where most of the server-side code for dealing with clients is located. Here are the contents of the common/server/clientconnection.cs module.

 //----------------------------------------------------------------------------- // Torque Game Engine // // Copyright (c) 2001 GarageGames.com // Portions Copyright (c) 2001 by Sierra Online, Inc. //----------------------------------------------------------------------------- function GameConnection::OnConnectRequest( %client, %netAddress, %name ) {    Echo("Connect request from: " @ %netAddress);    if($Server::PlayerCount >= $pref::Server::MaxPlayers)       return "CR_SERVERFULL";    return ""; } function GameConnection::OnConnect( %client, %name ) {    MessageClient(%client,'MsgConnectionError',"",$Pref::Server::ConnectionError);    SendLoadInfoToClient( %client );    if (%client.getAddress() $= "local") {       %client.isAdmin = true;       %client.isSuperAdmin = true;    }    %client.guid = 0;    AddToServerGuidList( %client.guid );    // Set admin status    %client.isAdmin = false;    %client.isSuperAdmin = false;    // Save client preferences on the Connection object for later use.    %client.gender = "Male";    %client.armor = "Light";    %client.race = "Human";    %client.skin = AddTaggedString( "base" );    %client.SetPlayerName(%name);    %client.score = 0;    $instantGroup = MissionCleanup;    Echo("CADD: " @ %client @ " " @ %client.GetAddress());    // Inform the client of all the other clients    %count = ClientGroup.GetCount();    for (%cl = 0; %cl < %count; %cl++) {       %other = ClientGroup.GetObject(%cl);       if ((%other != %client)) {          MessageClient(%client, 'MsgClientJoin', "",             %other.name,             %other,             %other.sendGuid,             %other.score,             %other.IsAIControlled(),             %other.isAdmin,             %other.isSuperAdmin);       }    }    // Inform the client we've joined up    MessageClient(%client,       'MsgClientJoin', '\c2Welcome to the Torque demo app %1.',       %client.name,       %client,       %client.sendGuid,       %client.score,       %client.IsAiControlled(),       %client.isAdmin,       %client.isSuperAdmin);    // Inform all the other clients of the new guy    MessageAllExcept(%client, -1, 'MsgClientJoin', '\c1%1 joined the game.',       %client.name,       %client,       %client.sendGuid,       %client.score,       %client.IsAiControlled(),       %client.isAdmin,       %client.isSuperAdmin);    // If the mission is running, go ahead and download it to the client    if ($missionRunning)       %client.LoadMission();    $Server::PlayerCount++; } function GameConnection::SetPlayerName(%client,%name) {    %client.SendGuid = 0;    // Minimum length requirements    %name = StripTrailingSpaces( StrToPlayerName( %name ) );    if ( Strlen( %name ) < 3 )       %name = "Poser";    // Make sure the alias is unique, we'll hit something eventually    if (!IsNameUnique(%name))    {       %isUnique = false;       for (%suffix = 1; !%isUnique; %suffix++) {          %nameTry = %name @ "." @ %suffix;          %isUnique = IsNameUnique(%nameTry);       }       %name = %nameTry;    }    // Tag the name with the "smurf" color:    %client.nameBase = %name;    %client.name = AddTaggedString("\cp\c8" @ %name @ "\co"); } function IsNameUnique(%name) {    %count = ClientGroup.GetCount();    for ( %i = 0; %i < %count; %i++ )    {       %test = ClientGroup.GetObject( %i );       %rawName = StripChars( detag( GetTaggedString( %test.name ) ), "\cp\co\c6\c7\c8\c9" );       if ( Strcmp( %name, %rawName ) == 0 )          return false;    }    return true; } function GameConnection::OnDrop(%client, %reason) {    %client.OnClientLeaveGame();    RemoveFromServerGuidList( %client.guid );    MessageAllExcept(%client, -1, 'MsgClientDrop', '\c1%1 has left the game.', %client.name, %client);    RemoveTaggedString(%client.name);    Echo("CDROP: " @ %client @ " " @ %client.GetAddress());    $Server::PlayerCount--;    if( $Server::PlayerCount == 0 && $Server::Dedicated)       Schedule(0, 0, "ResetServerDefaults"); } function GameConnection::StartMission(%this) {    CommandToClient(%this, 'MissionStart', $missionSequence); } function GameConnection::EndMission(%this) {    CommandToClient(%this, 'MissionEnd', $missionSequence); } function GameConnection::SyncClock(%client, %time) {    CommandToClient(%client, 'syncClock', %time); } function GameConnection::IncScore(%this,%delta) {    %this.score += %delta;    MessageAll('MsgClientScoreChanged', "", %this.score, %this); } 

The following functions and GameConnection methods are defined in the ClientConnection module:

    GameConnection::OnConnectRequest    GameConnection::OnConnect    GameConnection::SetPlayerName    IsNameUnique    GameConnection::OnDrop    GameConnection::StartMission    GameConnection::EndMission    GameConnection::SyncClock    GameConnection::IncScore 

The method GameConnection::OnConnectRequest is the server-side destination of the client-side GameConnection::Connect method. We use this method to vet the request—for example, examine the IP address to compare to a ban list, or make sure that the server is not full, and stuff like that. We have to make sure that if we want to allow the request, we must return a null string ( " " ).

The next method, GameConnection::OnConnect, is called after the server has approved the connection request. We get a client handle and a name string passed in as parameters. The first thing we do is ship down to the client a tagged string to indicate that a connection error has happened. We do not tell the client to use this string. It's just a form of preloading the client.

Then we send the load information to the client. This is the mission information that the client can display to the user while the mission loading process takes place. After that, if the client also happens to be the host (entirely possible), we set the client to be a superAdmin.

Then we add the client to the user ID list that the server maintains. After that there are a slew of game play client settings we can initialize.

Next, we start a series of notifications. First, we tell all clients that the player has joined the server. Then we tell the joining player that he is indeed welcome here, despite possible rumors to the contrary. Finally, we tell all the client-players that there is a new kid on the block, so go kill him. Or some such—whatever you feel like!

After all the glad-handing is done, we start downloading the mission data to the client starting the chain of events depicted back there in Figure 7.2.

GameConnection::SetPlayerName does some interesting name manipulation. First, it tidies up any messy names that have leading or trailing spaces. We don't like names that are too short (trying to hide something?), so we don't allow those names. Then we make sure that the name is not already in use. If it is, then an instance number is added to the end of the name. The name is converted to a tagged string so that the full name only gets transmitted once to each client; then the tag number is used after that, if necessary.

The function IsNameUnique searches through the server's name list looking for a match. If it finds the name, then it isn't unique; otherwise it is.

The method GameConnection::OnDrop is called when the decision is made to drop a client. First, the method makes a call to the client so that it knows how to act during the drop. Then it removes the client from its internal list. All clients (except the one dropped) are sent a server text message notifying them of the drop, which they can display. After the last player leaves the game, this method restarts the server. For a persistent game, this statement should probably be removed.

The next method, GameConnection::StartMission, simply notifies clients whenever the server receives a command to start another server session in order to give the clients time to prepare for the near-future availability of the server. The $missionSequence is used to manage mission ordering, if needed.

Next, GameConnection::EndMission is used to notify clients that a mission is ended, and hey! Stop playing already!

The method GameConnection::SyncClock is used to make sure that all clients' timers are synchronized with the server. You can call this function for a client anytime after the mission is loaded, but before the client's player has spawned.

Finally, the method GameConnection::IncScore is called whenever you want to reward a player for doing well. By default, this method is called when a player gets a kill on another player. When the player's score is incremented, all other players are notified, via their clients, of the score.

The Game Module

The server-side Game module is the logical place to put server-specific game play features. Here are the contents of the common/server/game.cs module.

 //----------------------------------------------------------------------------- // Torque Game Engine // // Copyright (c) 2001 GarageGames.com // Portions Copyright (c) 2001 by Sierra Online, Inc. //----------------------------------------------------------------------------- function OnServerCreated() {    $Server::GameType = "Test App";    $Server::MissionType = "Deathmatch"; } function OnServerDestroyed() {    DestroyGame(); } function OnMissionLoaded() {    StartGame(); } function OnMissionEnded() {    EndGame(); } function OnMissionReset() {    // stub } function GameConnection::OnClientEnterGame(%this) {    //stub } function GameConnection::OnClientLeaveGame(%this) {    //stub } //----------------------------------------------------------------------------- // Functions that implement game-play //----------------------------------------------------------------------------- function StartGame() { //stub } function EndGame() { //stub } 

The following functions and GameConnection methods are defined in the Game module:

    OnServerCreated    OnServerDestroyed    OnMissionLoaded    OnMissionEnded    OnMissionReset    StartGame    EndGame    GameConnection::OnClientEnterGame    GameConnection::OnClientLeaveGame 

The first function defined, OnServerCreated, is called from CreateServer when a server is constructed. It is a useful place to load server-specific datablocks.

The variable $Server::GameType is sent to the master, if one is used. Its purpose is to uniquely identify the game and distinguish it from other games handled by the master server. The variable $Server::MissionType is also sent to the server—clients can use its value to filter servers based on mission type.

The next function, OnServerDestroyed, is the antithesis of OnServerCreated—anything you do there should be undone in this function.

The function OnMissionLoaded is called by LoadMission once a mission has finished loading. This is a great location to initialize mission-based game play features, like perhaps calculating weather effects based on a rotating mission scheme.

OnMissionEnded is called by EndMission just before it is destroyed; this is where you should undo anything you did in OnMissionLoaded.

OnMissionReset is called by ResetMission, after all the temporary mission objects have been deleted.

The function GameConnection::OnClientEnterGame is called for each client after it has finished downloading the mission and is ready to start playing. This would be a good place to load client-specific persistent data from a database back end, for example.

GameConnection::OnClientLeaveGame is called for each client that is dropped. This would be a good place to do a final update of back-end database information for the client.

Although we don't use a great deal of the functions in this module, it is a great location for a lot of game play features to reside.




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