Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

3D Game Programming All In One (2004)

.pdf
Скачиваний:
139
Добавлен:
17.08.2013
Размер:
17.91 Mб
Скачать

648Chapter 23 The Game Client

add some code to provide the player the ability to choose between playing maps in sequence (as exists now) or randomly.

You will want to have the Sequence and Random buttons that I've already provided in HostScreen.gui set a variable that your onWake code can examine. If the variable has one value, leave things as they are. If the variable has a different value, then have the onWake method choose a map randomly. One simple method to introduce the randomness is to select a random value between 0 and the number of available maps, and then to reject that many maps when the findNextFile function returns them. Then you would accept the next map returned.

Give it a try.

FindServer Interface Code

You saw the FindServer interface way back there in Figure 23.4. It lets you browse for servers with which you can connect. We've already looked at how this part of Torque works back in Chapter 5, 6, and 7, so I won't go into too much detail here. The functional code for the FindServer interface, extracted from ServerScreen.cs, is shown here for a brief discussion:

function ServerScreen::onWake()

{

MasterJoinServer.SetActive(MasterServerList.rowCount() > 0);

}

function ServerScreen::Query(%this)

{

QueryMasterServer(

 

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

}

Team LRN

Client Code

649

function ServerScreen::Join(%this)

{

CancelServerQuery();

%id = MasterServerList.GetSelectedId();

%index = getField(MasterServerList.GetRowTextById(%id),6); if (SetServerInfo(%index)) {

%conn = new GameConnection(ServerConnection); %conn.SetConnectArgs($pref::Player::Name); %conn.SetJoinPassword($Client::Password); %conn.Connect($ServerInfo::Address);

}

}

function ServerScreen::Close(%this)

{

cancelServerQuery();

Canvas.SetContent(MenuScreen);

}

function ServerScreen::Update(%this)

{

ServerQueryStatus.SetVisible(false);

ServerServerList.Clear(); %sc = getServerCount();

for (%i = 0; %i < %sc; %i++) { setServerInfo(%i); ServerServerList.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); // ServerInfo index stored also

}

ServerServerList.Sort(0);

ServerServerList.SetSelectedRow(0);

ServerServerList.scrollVisible(0);

ServerJoinServer.SetActive(ServerServerList.rowCount() > 0);

}

function onServerQueryStatus(%status, %msg, %value)

{

if (!ServerQueryStatus.IsVisible())

Team LRN

650 Chapter 23 The Game Client

ServerQueryStatus.SetVisible(true); switch$ (%status) {

case "start": ServerJoinServer.SetActive(false); ServerQueryServer.SetActive(false); ServerStatusText.SetText(%msg); ServerStatusBar.SetValue(0); ServerServerList.Clear();

case "ping":

ServerStatusText.SetText("Ping Servers");

ServerStatusBar.SetValue(%value);

case "query":

ServerStatusText.SetText("Query Servers");

ServerStatusBar.SetValue(%value);

case "done": ServerQueryServer.SetActive(true); ServerQueryStatus.SetVisible(false); ServerScreen.update();

}

}

Here the OnWake method makes the list active if there is anything already available from a previous incarnation to list. It's invoked as soon as the interface object is displayed on the screen.

When you click the Query Master button, the Query method is called, which sends a query packet to the master server, informing the master about what sort of servers are of interest. If the master server returns any information, it is deposited in the server information list, the Update method is invoked, and the list is created on the screen. This back-and- forth transaction is described in greater detail in Chapter 6.

The onServerQueryStatus method handles the various responses from the master server and deposits returned information, according to the changing states, into the various fields of the list.

ChatBox Interface Code

Open the file C:\koob\control\client\Initialize.cs and add the following lines to the function InitializeClient:

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

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

Team LRN

Client Code

651

These exec statements load the new files that will provide our chat interface. You can copy them from C:\3DGPAi1\RESOURCES\CH23 and put them into the directories under the C:\koob\control\client\ directory in the subdirectories specified in the exec statements.

The ChatBox interface receives its text via a rather convoluted route. The message text originates at one of the clients and is sent to the server. The server receives the typed message and passes it to some common code that handles chat messages between the server and the client. Once the message arrives at the client common code, it is passed to the message handler called onChatMessage, which we provide in our client control code in our ChatBox.cs module. There is a parallel handler we are expected to supply in our client control code called onServerMessage, which is essentially the same as the one for the chat messages. These two functions look like this:

function onChatMessage(%message, %voice, %pitch)

{

if (GetWordCount(%message)) { ChatBox.AddLine(%message);

}

}

function onServerMessage(%message)

{

if (GetWordCount(%message)) { ChatBox.AddLine(%message);

}

}

Not much needed here—just add the new text to the ChatBox object using its AddLine method.

The AddLine method is where all the heavy lifting is done; it looks like this:

function ChatBox::addLine(%this,%text)

{

%textHeight = %this.profile.fontSize; if (%textHeight <= 0)

%textHeight = 12;

%chatScrollHeight = getWord(%this.getGroup().getGroup().extent, 1); %chatPosition = getWord(%this.extent, 1) - %chatScrollHeight +

getWord(%this.position, 1);

%linesToScroll = mFloor((%chatPosition / %textHeight) + 0.5); if (%linesToScroll > 0)

%origPosition = %this.position;

while( !chatPageDown.isVisible() && MsgBoxMessageVector.getNumLines() && (MsgBoxMessageVector.getNumLines() >= $pref::frameMessageLogSize))

Team LRN

652 Chapter 23 The Game Client

{

%tag = MsgBoxMessageVector.getLineTag(0); if(%tag != 0)

%tag.delete();

MsgBoxMessageVector.popFrontLine();

}

MsgBoxMessageVector.pushBackLine(%text, $LastframeTarget); $LastframeTarget = 0;

if (%linesToScroll > 0)

{

chatPageDown.setVisible(true); %this.position = %origPosition;

}

else chatPageDown.setVisible(false);

}

We start out by getting the font size from the profile. We need this in order to determine the height and width spacing requirements for scrolling and frame sizing.

Then we use getGroup to obtain the handle for the object group this control belongs to. And we use that handle to get the parent group's handle. Then we use that handle to get the extent property that tells us the height and width of the parent object. We take the second value in the extent—which is the height—by using getWord to get word number 1, which is actually the second word. (We perverted programmers usually count starting at 0 instead of 1—but not always!)

The object retains the current output position using the position parameter, and that is used to calculate where the next position will be and saved as %chatPosition. We then use the calculations to figure out %linesToScroll, which dictates the text scroll action and the scroll bar actions.

Next, we enter a loop that extracts text from the text buffer called MsgBoxMessageVector line by line and inserts the lines in the ChatBox control.

Finally, we adjust the visibility of the scroll down prompt based on whether or not our position causes text to be out of sight at the bottom of the display.

MessageBox Interface Code

The MessageBox interface accepts our input from the keyboard.

We need to add a message handler to the server to receive the typed messages when they are sent from the client. Because of the context, it makes more sense to do that here than in Chapter 22, even though we are dealing with client issues in this chapter.

Team LRN

Client Code

653

Open the file C:\koob\control\server\server.cs and add the following function to the end of the file:

function serverCmdTypedMessage(%client, %text)

{

if(strlen(%text) >= $Pref::Server::MaxChatLen)

%text = getSubStr(%text, 0, $Pref::Server::MaxChatLen); ChatMessageAll(%client, '\c4%1: %2', %client.name, %text);

}

This handler grabs the incoming typed message, makes sure that it isn't too long (we may want to restrict chat messages in order to preserve bandwidth requirements), and then sends the message to the common code server function called ChatMessageAll. The ChatMessageAll function will distribute the message to all of the other clients logged in to our game.

Next, let's look at the code that manages this on behalf of the MessageBox interface:

function MessageBox::Open(%this)

{

%offset = 6; if(%this.isVisible())

return;

%windowPos = "8 " @ ( getWord( outerChatFrame.position, 1 ) + getWord( outerChatFrame.extent, 1 ) + 1 );

%windowExt = getWord( OuterChatFrame.extent, 0 ) @ " " @ getWord( MessageBox_Frame.extent, 1 );

%textExtent = getWord(MessageBox_Text.extent, 0); %ctrlExtent = getWord(MessageBox_Frame.extent, 0); Canvas.pushDialog(%this); MessageBox_Frame.position = %windowPos; MessageBox_Frame.extent = %windowExt;

MessageBox_Edit.position = setWord(MessageBox_Edit.position, 0, %textExtent + %offset);

MessageBox_Edit.extent = setWord(MessageBox_Edit.extent, 0, %ctrlExtent - %textExtent - (2 * %offset));

%this.setVisible(true);

deactivateKeyboard(); MessageBox_Edit.makeFirstResponder(true);

}

function MessageBox::Close(%this)

{

if(!%this.isVisible())

return;

Team LRN

MessageBox_Edit

654 Chapter 23 The Game Client

Canvas.popDialog(%this);

%this.setVisible(false); if ( $enableDirectInput ) activateKeyboard();

MessageBox_Edit.setValue("");

}

function MessageBox::ToggleState(%this)

{

if(%this.isVisible())

%this.close(); else

%this.open();

}

function MessageBox_Edit::OnEscape(%this)

{

MessageBox.close();

}

function MessageBox_Edit::Eval(%this)

{

%text = trim(%this.getValue()); if(%text !$= "")

commandToServer('TypedMessage', %text); MessageBox.close();

}

function ToggleMessageBox(%make)

{

if(%make)

MessageBox.toggleState();

}

The Open method does some assignments of local variables based on the settings of properties of the MainChatBox object. This is so we can place the message box into a position relative to the chat display; in this case we are going to put it below and offset a little bit to the right.

Once we've done this, the code loads the MessageBox control into the Canvas using

Canvas.pushDialog(%this), where %this is the handle of the MessageBox control object, and positions it according to the values of the earlier saved local variables.

When we've completed the positioning of the control, then the code makes it visible.

Next, the code turns off keyboard input for the Canvas object and sets the MessageBox_Edit subobject responsible for handling key inputs. From this point on, all typing goes into the

subobject, until something changes that.

Team LRN

Game Cycling 655

The Close method removes the control from the Canvas, makes the control invisible again, and restores keyboard input handling to the Canvas.

The ToggleState method merely opens or closes the message box in a toggle fashion. If the control is open, it closes it, and vice versa.

The OnEscape method closes the control. This method is defined as the escapeCommand property value in the interface definition in MessageBox.gui.

The Eval method obtains the entered text, trims empty spaces from the end, and sends the text to the server as the parameter for a TypedMessage message, which the server knows how to handle.

Finally, the ToggleMessageBox method is bound to the "t" key in our presets.cs file. When it receives a non-null value in %make, it changes the current MessageBox open state using the

ToggleState method.

Game Cycling

The final feature we need to implement is the ability to cycle games when they are over— that is, when a player has reached either the score limit or the time limit.

First, add the following functions to the end of C:\koob\control\server\server.cs:

function cycleGame()

{

if (!$Game::Cycling) { $Game::Cycling = true;

$Game::Schedule = schedule(0, 0, "onCycleExec");

}

}

function onCycleExec()

{

endGame();

$Game::Schedule = schedule($Game::EndGamePause * 1000, 0, "onCyclePauseEnd");

}

function onCyclePauseEnd()

{

$Game::Cycling = false;

%search = $Server::MissionFileSpec;

for (%file = findFirstFile(%search); %file !$= ""; %file = findNextFile(%search)) {

if (%file $= $Server::MissionFile) { %file = findNextFile(%search);

Team LRN

$Game::MaxPoints

656 Chapter 23 The Game Client

if (%file $= "")

%file = findFirstFile(%search);

break;

}

}

loadMission(%file);

}

The first function, cycleGame, schedules the actual cycling code to occur at some later point. In this case we do it right away after making sure that we aren't actually already cycling.

The function nCycleExec actually ends the game. The endGame function just stops when it finishes, not doing anything else. Further action is scheduled to be taken by the onCyclePauseEnd function. This allows us to put up a victory screen or other messages and leave them up for an appropriate viewing time before continuing on to the next game.

In order to provoke the actual cycleGame function into being, we do two things. First, when the game is launched, we schedule its demise based on $Game::Duration. To do this, locate the function StartGame farther up in the server.cs file, and add these lines to the beginning:

if ($Game::Duration)

$Game::Schedule = schedule($Game::Duration * 1000, 0, "CycleGame" );

This starts the game timer running. When it expires, it invokes the cycleGame function.

The other thing we need to do is add some code that checks to see if a player has hit the limit.

Locate the function GameConnection::DoScore() and add this code to the top of the function:

%client.score = (%client.lapsCompleted * $Game::Laps_Multiplier) + (%client.money * $Game::Money_Multiplier) + (%client.deaths * $Game::Deaths_Multiplier) + (%client.kills * $Game::Kills_Multiplier) ;

This code accumulates the various scoring values into a single overall score. Now add the following code to the end of the same DoScore function:

if (%client.score >= $Game::MaxPoints)

cycleGame();

This causes the game cycling activity to happen if any one player hits the score limit. Game cycling entails ending the game, loading a new map, and dropping the players into the game in the new map.

Team LRN

Moving Right Along

657

Final Change

The final, very, very last piece of code we are going to change will allow us to remain in the program after we exit a game. Previously, when we exited a game using the Escape key, the program quit. This final change tidies that up for us. Open the file C:\koob\control\ client\misc\presetkeys.cs and locate the function DoExitGame() and change it to match the following:

function DoExitGame()

{

if ( $Server::ServerType $= "SinglePlayer" )

MessageBoxYesNo( "Exit Mission", "Exit?", "disconnect();", ""); else

MessageBoxYesNo( "Disconnect", "Disconnect?", "disconnect();", "");

}

This function now checks to see if we are in singleor multiplayer mode. It does this to provide a customized exit prompt depending on which mode it is. In any event, the disconnect function is called to break the connection with the game server.

Moving Right Along

So there you have it. I hope your fingers aren't worn to the bone. You can see that there is a great deal of power available to those worn fingertips. I'm sure that as you've progressed through the preceding chapters, your head began to fill with all sorts of ideas about things you might want to do. In the next and final chapter of the book, I have a few things I want to say on that topic.

Team LRN