Chapter V

Starting a Multiplayer Game

 

Multiplayer Commands and Variables to Study for This Chapter

connection, server, client, on_server, on_client, event_join, event_var, send_var, -sv, -cl, -pl, -ip, -sn.

Resources needed for this chapter

Cbabe.mdl , Red_Warlock.mdl, Blue_Wizard.mdl (in Entities folder), Centur12.pcx , Arrow.pcx (in Bitmaps Folder)

 

Note: The Cbabe model's animation names are different from the standard Cbabe model, so use the one in the resources folder.

Note: I am assuming you are familiar with common C-Script commands and know how to start a single player game.

 

  Ok, lets get into some scripting now. How do we go about starting a multiplayer game? Of course, depending on your game, you could start it in various ways. What I will try to do here is show you all the variables and commands that you can use to get the game started like you want.

  As I mentioned in Chapter I, it is important for us to be able to distinguish whether the machine running the program is a server, host, client, or single player. You can write a game in 3DGS so it will work as both multiplayer and single player.

  Although it will be a bit more complicated, I am going to go ahead and write this code so we can start this game in all possible connection modes. So it can be ran as a dedicated server, host computer, a client computer, or single player. I was planning to cover Artificial Intelligence, but I have run out of time. So, although you can run the game as single player it might be somewhat boring with nothing to shoot at but the walls.

 

Understanding Command Line Options in 3DGS

  We first need to understand what command line options will be needed upon first running the game, so that once the game starts we use the command definitions to determine what parts of the script this computer is suppose to be running. All of the command line options that pertain specifically to multiplayer should be listed in Appendix I. We will not cover all of them in this tutorial, but we will use quite a few of them. (-sv, -cl, -pl, and -ip) Look at them and get a general understanding of what they mean.

  Here is a list of some possible command lines for a multiplayer capable game:

A single player mode

 (no multiplayer commands used)

A single player mode assigning the player a name

 -pl PlayerName

A client over LAN

 -cl

A client on LAN assigning the client a player name

 -cl -pl PlayerName

A client over Internet

-cl -ip IPAddressOfServer

A client over Internet assigning the client a player name

-cl -ip IPAddressOfServer -pl PlayerName

A client over Internet assigning the client a player name and trying to connect to specific session

-cl -ip IPAddressOfServer -pl PlayerName -sn SessionName

A host over LAN or Internet

 -sv -cl

A host over LAN or Internet assigning the host a player name

 -sv -cl -pl PlayerName

A host over LAN or Internet assigning the host a player name and starting a specific session

 -sv -cl -pl PlayerName -sn SessionName

A dedicated server on Internet (Pro)

 -sv

A dedicated server on Internet starting a specific session (Pro)

 -sv -sn SessionName

  Hopefully that list will give you an idea of some combinations that could be used as command lines. This tutorial should be able to run quite a few of the above command lines by the time we are done in combination with and other acceptable commands like -wnd (with the exception of dedicated server if you do not own A6 Pro).

 

The Connection Variable:

  The connection variable is a the most important multiplayer variable when trying to figure out what activities the computer is supposed to be performing in a multiplayer game. Once a connection is established or created, the connection variable can be have the following possible values:

connection == 0 No connection to Multiplayer system

connection == 1 Connection as Server to Multiplayer system

connection == 2 Connection as Client to Multiplayer system

connection == 3 Connection as Server and Client to Multiplayer system

  In a minute, we will be using this variable to help us start the game, but first we need to cover some other important variables and commands also.

 

The Server and Client Variables:

  The server and client variables can be used with ifdef, ifelse, and endif statements to help determine whether a computer is running as server, client, or both. These variables are especially useful when first starting game and no connection has made yet.

  Here is a good example of how they might be used before a connection is made:

// if not a single player game, wait for connection

ifdef server;

while(connection== 0) {wait(1);} // wait until level loaded and connection set

endif;

 

ifdef client;

while(connection== 0) {wait(1);} // wait until level loaded and connection set

endif;

  It may look a bit strange at first, but that is actually a very powerful few lines of code. It gives the ability for the game to start in any connection mode (dedicated server, host, client, or single player). Let's revisit some of the command lines we listed above and explore what would happen if they were used and ran into these lines of code.

A single player mode:

 (no multiplayer commands used)

  If we had no multiplayer commands in command line, when the program reached these lines of code it would skip both the ifdef server & ifdef client commands since they were not defined in command line. The result would be the game would continue and the connection variable would equal 0. We don't want to wait for a connection in a single player mode (if we did, we would end up with an endless loop). So the ifdef server & ifdef client now allow us to get past waiting for a connection in single player mode.

A client over LAN:

 -cl

  If we are a client computer, we will wait for the connection to made in the ifdef client statement and the connection variable would equal 2 once connection is made . It would bypass the ifdef server statement.

A host over LAN or Internet:

 -sv -cl

  If we are a host computer, we will wait for the connection to made in the ifdef server statement and the connection variable would equal 3 once connection is made . It would then also go into the ifdef client statement, but imediately exit, because connection != 0.

A dedicated server on internet or LAN (Pro):

 -sv

  If we are a dedicated server, we will wait for the connection to made in the ifdef server statement and the connection variable would equal 1 once connection is made . It would bypass the ifdef client statement.

 

Brain Teaser 1: Why wouldn't this variation of the code above work to start game in any of the four possible connection modes?

// if not a single player game, wait for connection

ifdef server;

while(connection== 0) {wait(1);} // wait until level loaded and connection set

ifelse; // not server

while(connection== 0) {wait(1);} // wait until level loaded and connection set

endif;

  Answer in Appendix V.

 

The On_Server = Function and On_Client = Function Commands:

On_Server = Function;

  Whenever data is sent to the server, the Function specified will be called. Can also be used with event_join variable to determine how to start the game for a client.

On_Client = Function;

 Whenever data is sent to the client, the Function specified will be called.

 

 These commands are useful when certain data is sent you can take that data and manipulate it if you desire.

 

Let's Start A Multiplayer Game:

  We should have enough information now to start a multiplayer game. Let's start the program that so each player is assigned a player number so we can keep track of the players. We will go ahead and set the game up so it can be played as single player, multiplayer, or MMO (dedicated server). So buckle up, this chapter could be a long rough ride.

  First let's get the program initialized and add all of our known skills from our game documentation.

  Being a C++ programmer, I can't stand not having TRUE & FALSE defines for if statement's so I always add them. 3DGS has ON & OFF, which I do use for flags, but I am a TRUE & FALSE kind of guy. So let's add the light text.

 

path ".\\Bitmaps";

 

//--------------------------------------------------------------------

// DEFINES

//--------------------------------------------------------------------

// Boolean

define FALSE, 0;

define TRUE, 1;

 

  Ok, lets go ahead and define the maximum number of people who can be connected to the game at one time. I put 64 connections in since I have A6 Pro. Under TRUE and FALSE defines at this define.

 

Note: If you have A6 Commercial, you should change it to 4 connections. A6 Pro is not limited to 64 players, that value was just set by me for this tutorial and can be changed if desired.

 

define TRUE, 1;

 

define MAX_CONNECTIONS, 64; // maximum number of people who can be connected to server

 

    While we are here in the defines department, let's go ahead and define our 3 possible professions to make the code easier to read. Especially the tons of if statements we will eventually have. So under MAX_CONNECTIONS define add the profession defines.

 

define MAX_CONNECTIONS, 64; // maximum number of people who can be connected to server

 

// Profession Types

define PROF_NUCLEAR_SCIENTIST, 1; // Nuclear scientist

define PROF_BIOLOGICAL_SCIENTIST, 2; // Biological scientist

define PROF_OPERATIONS_OFFICER, 3; // Operations officer

 

Brain Teaser 2: Why is it a good idea to define a variable like

define PROF_NUCLEAR_SCIENTIST, 1; // Nuclear scientist

and use it in an if statement like

if (my.profession == PROF_NUCLEAR_SCIENTIST)

instead of just doing an if statement like so?

if (my.profession == 1)

 

  Answer in Appendix V.

 

  Some times it can be difficult to know where to start, but having good game documentation makes it much easier. Right below the profession defines, go ahead and add our basic player skill defines like below. These are in our game documentaion too. Our skills will continue to grow as we progress and figure out new things we need as skills, but this is a good start.

 

define PROF_BIOLOGICAL_SCIENTIST, 2; // Biological scientist

define PROF_OPERATIONS_OFFICER, 3; // Operations officer

 

//--------------------------------------------------------------------

// SKILL DEFINITIONS

//--------------------------------------------------------------------

define player_number, skill1; // player number this entity owned by

define lunar_psychosis_rating, skill2; // score

define profession, skill3; // entity's profession

define speed, skill4; // move speed

define health, skill5; // health

define max_health, skill6; // max health of entity

define armor_class, skill7; // armor class of ent

 

Note: You may notice how I put header comments before all similar defines, declarations, and functions. This once again is to make things easy for you and your team to find. Organization is a good thing.

 

  We are going to be using the infamous CBabe model in this chapter so let's go ahead and declare the models in our resources declarations.

 

//--------------------------------------------------------------------

// RESOURCES

//--------------------------------------------------------------------

 

// Levels

string world_str = <multiplayer.wmb>; // level string

 

//Models

string str_cbabe = <cbabe.mdl>; // CBabe Model

 

Note: The CBabe model in this tutorial has different animation names then the original CBabe model. So use the one provided in this tutorial. It can be found in the entities folder.

 

  Let's go ahead and set some engine variables.

 

string str_cbabe = <cbabe.mdl>; // CBabe Model

 

//--------------------------------------------------------------------

// Variables

//--------------------------------------------------------------------

 

// Engine Variable

var fps_max = 60; // lock fps at 60 for all players

var fps_lock = ON;

// The engine starts in the resolution given by the following vars.

var video_mode = 7; // screen size 800x600

var video_screen = 1; // start settings for Fullscreen

var mouse_mode = 2; // does not effect forces

 

  First, we lock the FPS (frames per second) on all the of players computers, so one player does not gain an advantage because he has a faster computer. Next, we set the video information. Also, we set the mouse mode so the mouse does not effect 3DGS's force variable.

 

Note: You can change video_screen to equal 2 (windowed) if you like for easier testing.

 

Connecting Players and Keeping Count of Connections

  Since we are trying to connect players to the game, we should probably some variables to track how many players are actually connected and how many are actually playing game (selected a profession). Now would be a good time to explain how joining a game will work. A client will join the session , which will increase the number of people_connected variable, while he is deciding what profession he wants to be, he will actually be able to see the other players fighting. Also, he can note how many ammo packs are lying around. So he might want to become a nuclear scientist if he see a lot of nuclear waste in the level. After he selects profession and becomes a actual player, the number_of_players variable will be incremented. The people_connected variable is actually the maximum number of players allowed by 3DGS (4 players for Commercial). So if you are using A6.30 Commercial you can only actually have 4 people connected at a time and up to 4 actual players. So add the following lines of code below the engine variables

 

var mouse_mode = 2; // does not effect forces

 

// Game Variables

var people_connected = 0; // number of people connected to server

var number_of_players = 0; // # of players in game

 

  We have 0 people connected and 0 players playing game to start with.

  Ok, we will be adding more variables as we go along as need be. The people_connected and number_of_players should give us a foundation to work off of.

  Let's go ahead and work the people connected code first. Remember earlier in the chapter when we discussed the server and client variables? I gave an example snippet of code that we are going to use now. So add this code in function main() before the level is loaded ( I put before level load due to an A6.30 beta bug, it would be better if it was after the level load and will probably be ok to put it there when the final A6.30 is released, that way we could be loading the level while waiting for the connection to be made).

 

// if not a single player game, wait for connection

ifdef server;

while(connection== 0) {wait(1);} // wait until level loaded and connection set

endif;

 

ifdef client;

while(connection== 0) {wait(1);} // wait until level loaded and connection set

endif;

 

level_load(world_str); // load level, must be loaded after connection is set

 

  I explained this code earlier. What it does is, if the player is a client or server we want to wait until connection is made and set. We use the connection variable in many if statements. If it is not set before the actual game code, the script will think it is running in single player mode and will start initializing the game that way, and then when the connection is actually made, the program will be very confused and terrible things will most certainly happen. Can you say, "Crash!?"

  Now we need to figure out a way for the server or host to keep a count on the number of people connected and also make sure we don't exceed our maximum number of connections (MAX_CONNECTIONS) we want possible. To do this, we will use the on_server = function command.

  Go ahead and add the following code at the bottom of the wdl file.

 

//--------------------------------------------------------------------

// Function Server_Called(): server was called

//--------------------------------------------------------------------

function server_called()

{

// if new player connected, increment people connected

if ((event_type == event_join) && (people_connected < MAX_CONNECTIONS))

{

ifdef server;

people_connected += 1; // another person connected

send_var(people_connected); // send number of people connected

endif; // ifdef server

}

 

// some one disconnected

if (event_type == event_leave)

{

ifdef server;

people_connected -= 1; // one less person connected to server

send_var(people_connected); // send number of people connected

endif;

}

}

 

on_server = server_called; // on server event, call server_called()

 

  Ok, when a player connects or disconnects a server event happens, which causes the on_server = server_called command to be executed. Once in the server_called function, we check what kind of event got us here.

  First we check if the server event was a new person connecting to the session making sure we don't exceed what we have set for our maximum connections.

if ((event_type == event_join) && (people_connected < MAX_CONNECTIONS))

  If this was the event we go ahead and increment our people_connected variable to reflect one more person connected and send it to all the clients so they also know how many people are connected to the game.

ifdef server;

people_connected += 1; // another person connected

send_var(people_connected); // send number of people connected

endif; // ifdef server

 Secondly, we check to see if the event was a disconnection, if (event_type == event_leave). If it was then we decrement the number of people connected to session and send it to all clients.

// some one disconnected

if (event_type == event_leave)

{

ifdef server;

people_connected -= 1; // one less person connected to server

send_var(people_connected); // send number of people connected

endif;

}

 That code is actually fairly simple to understand.

  But now we have a problem, that you wouldn't know about unless you have written some 3DGS multiplayer code before. The problem pertains to the Host (-cl -sv). Let's say, we own A6 Commercial and can only have 4 players in game. The host will be considered a player, but here is where the problem is, since the Host is the server as well as a client, when he runs the game, the event_join is never flagged so the on_server = server_called function is never called. Which means, after he runs the game, the people_connected variable will still equal 0. We are using this variable to track how many actual players there are (how many people running the game at same time), so we need to consider the Host a player too. So if a Host starts a game, we need to make sure he is considered a person connected, thus people_connected needs to equal 1 immediately after he starts game. So let's add a little fix for this Host problem. Add this code below level_load(). This kind of problem will show up again later guaranteed. By the time we handle 2 or 3 more times, you will start to understand it better. I call it "The Client is the Server Stupid!" or the "Why doesn't the Server want to call the Server?" problem.

 

level_load(world_str); // load level

sleep(.5); // make sure level is loaded

 

// if host, count yourself as a connection

if (connection == 3)

{

people_connected = 1;

}

 

  Before we get to assigning players their player numbers, which will be more difficult, lets go ahead and add some text so we can see how many people are connected to the game.

  Under game variables in declarations lets declare the string that will display the number of people connected.

 

// Game Variables

var people_connected = 0; // number of people connected to server

var number_of_players = 0; // # of players in game

 

//--------------------------------------------------------------------

// Strings

//--------------------------------------------------------------------

// display strings

string str_people_connected; // number of people connected to server

 

  Directly under that, let's add the text to display that string.

 

//--------------------------------------------------------------------

// Text

//--------------------------------------------------------------------

 // text to display the number of people connected to game

text txt_people_connected

{

pos_x = 0;

pos_y = 65;

layer = 15;

font fnt_century12;

string str_people_connected;

}

 

  Since our text has a font we have not declared yet (fnt_century12), we better declare it now before we forget. Under our Cbabe model declaration in the Resources area add this code.

 

string str_cbabe = <cbabe.mdl>; // CBabe Model

 

// Fonts

font fnt_century12 = <Centur12.pcx>,16,20; // Century12 font

 

  Now that we have the text set up, let's find a place in main to make it visible. We could have used flags = visible; within the text itself, but because we are going to have a lot of text showing up later that all need to be initialized in a variety of ways, we will just set it up from the main() function. So let's add this code to the main() function.

 

if (connection == 3)

{

people_connected = 1;

}

 

// if dedicated server skip these

if (connection != 1)

{

// if not single player mode, display multiplayer information

if(connection)

{

txt_people_connected.visible = ON;

}

}

 

  This makes our people connected text visible, unless the computer is the dedicated server, which does not display anything or the game is being played in single player mode , in which case, we don't want to display multiplayer information. We are not done yet, we need to get our actual text message set up. To do this we will call a function that continually runs a loop to update the text we want to be shown. Add this function below the server_called function.

 

send_var(people_connected); // send number of people connected

endif;

}

}

 

//--------------------------------------------------------------------

// DISPLAY INFO - continuously displays game information

//--------------------------------------------------------------------

function display_info()

{

while(1)

{

str_cpy(str_people_connected, "People Connected: ");

str_for_num(str_temp, people_connected);

str_cat(str_people_connected, str_temp);

wait(1);

}

}

 

  We call this function once from main and after that it will update the people connected text to reflect how many people are connected every frame. If you was watching carefully, you might have noticed we added another string to our program to temporarily hold our people_connected string while converting it from a number, str_for_num(str_temp, people_connected). Let's declare that string now in our Strings declarations.

 

// display strings

string str_people_connected; // number of people connected to server

string str_temp; // temp string

 

  Ok, now we can add the call from within our main() function to continually update the text unless this computer is a dedicated server, in which case, since the dedicated server does not show the level, it does not need to run this function. It would just be unnecessary processing for the dedicated server, and the dedicated server will be busy enough processing all the actions of the players.

 

level_load(world_str); // load level, must be loaded after connection is set

 

// if dedicated server skip these

if(connection != 1)

{

display_info(); // continually display any information we want to show

 

// if not single player mode, display multiplayer information

if (connection)

{

txt_people_connected.visible = ON;

 }

}

 

 

  I think it's about to save the wdl file and see if it runs now. First let's set up SED so we can run the program from there without having to go back to WED each time. So from the menu select Options - Configuration. This should bring up a pop-up window like this.

  What you want to do now is set the 3D GameStudio Directory to your GStudio folder and then find the path to your multiplayer.wdl in the CSC/WDL file to run path. We will leave the command line empty for now (the command line usually go in the entry box that is in the Run Parameter area in the pop-up window) and then click OK.

  Ok now hit the Test Run Icon.

  You should just see the level with no text displayed since we just ran it in single player mode. Hit ESC to exit.

  Perhaps we should go ahead and actually do our first LAN test to see if we can get 2 people connected.

  I suppose now would be a good time to explain how to do this.

 

Note: Unless you have a Team Edition of 3DGS, in which case you can test run from WED and SED on 2 computers, you must do your scripting on one computer, create a executive file for your second computer. You can test run from SED on your primary computer and then run the executive game file on your secondary computer. If you get an run-time error, you must try to fix it on your main computer, and create an executive file again and copy it to secondary computer. It may not eloquent, but at least it's legal.

 

Note: When you add resources in to SED, you must exit WED if it is open, then restart it for WED to recognize new resources. If you didn't close and re-open WED and created an executive file using Resource or Publish, the executive file wouldn't recognize the CBabe model we just added.

 

  First, make sure your LAN is up and running. Create a executive file using either Resource or Publish in the File menu. Now copy the multiplayer.cd file to your secondary computer, remembering where you put it of course. Setting up a test folder on your secondary computer somewhere is a good idea. After you have done that, let's set up our primary computer to be a Host in SED by going Options - Configuration and adding the Host command line -cl -sv to the Run parameter's entry box like so.

 

  Then click OK. Go ahead and hit the Test Run Icon to see that it works. This is the last time I am going to say hit ESC to exit. You might notice that the Game Studio start up window now says 'Starting server...' with the Host command line where it didn't with no command line (single player). Here is what you should have seen this time. Since we are running in multiplayer mode (Host) the program should have displayed 1 person connected, that being the Host himself.

 

  Ok let's go over to our secondary computer now. We should already have the multiplayer.cd there since we copied it there earlier in chapter. Open up the multiplayer.cd folder. Now find multiplayer.exe file (It should have a A6 icon), right-click on it, the select Create Shortcut. You should see a Shortcut Icon to the executive file now in your folder like this.

  Now right-click on the Shortcut to multiplayer.exe icon, then select properties. Then find the Target: entry box and add -cl at the very end behind the path that is there like this (make sure you leave a space in between the "path" and -cl). Then hit OK.

  Now, let's run the program again from SED on our primary computer. Now on the secondary computer, double-click the Shortcut to multiplayer.exe icon. Amazing! We have just made our first multiplayer connection! We have 2 people connected now. This is what you should see on both computers. You will have to exit the game on both computers for now. Later we will add disconnecting code to automatically end game on client if the server quits.

  Let's do one more test now, Start the Host and then the Client, just like we did before. After the game is running on both computers, hit ESC to exit game on the Client's computer. Now look at the People Connected text. It now says 1. You have to love success!

  Congratulations are in order. This was our first step into multiplayer coding.

 

Creating a Player's Entity

  Now that we have managed to display how many people are connected to the game, it's time to get serious. We will now create a actual player entity and assign it a player number. We already declared the number_of_players variable and the player_number skill earlier in the chapter. We will be using those variables now and adding a few more too.

  For now we will just create a CBabe model for anyone who joins the game at a random location in the level, assigning it a player number. It sounds easy enough. Let's see if it is.

  First we will add a variable that lets the newest client know he has been recognized and it is ok for him to continue game set up. In declarations under Game variables add the server_says_start variable.

 

// Game Variables

var people_connected = 0; // number of people connected to server

var number_of_players = 0; // # of players in game

var server_says_start = FALSE; // client holds creating entity until server says go

 

  Now let's add the following lines of code to the server_called function.

 

// if new player connected, increment people connected

// and tell him it's ok to continue

if ((event_type == event_join) && (people_connected < MAX_CONNECTIONS))

{

ifdef server;

people_connected += 1; // another person connected

send_var(people_connected); // send number of people connected

server_says_start = TRUE; // send it's ok to go to newest client

send_var(server_says_start); // send start message

 

  Now in our main function add this under level_load().

 

level_load(world_str); // load level, must be loaded after connection is set

sleep(.5); // make sure level is loaded

 

if(connection == 2) // client?

{

while (server_says_start == FALSE) // wait for server to signal ok to continue

{

wait(1);

}

}

 

 Ok, this is how it works. A new Client joins, his server_says_start variable equals FALSE at game start up. The Server get's and event_join event and server_called function is called. The Server figures out the event was a player joining. The server then increments the number of people_connected , set's server_says_start to TRUE, and sends it to all the Clients. The secret we need to remember here is, that any older Clients that have already connected earlier have already been through this process and their server_says_start variable is already set to TRUE. So this will only effect the newest Client's server_says_start variable. Once the new Client receives the variable, he continues with the game set up. This code is just for assurance that all is going well with the server and client before letting the client continue.

 

Note: All the variables you declare on each client's computer are actually local to that computer until they are changed by the server using send_var, send_string, send_skill, etc.

 

  Now that we have that done with, it is time for some excitement. We are now going to actually create a model for every client that joins. We will go ahead and seed random using randomize() to start with since we will be placing the new players at random locations. So add this line of script to the main() function.

 

//--------------------------------------------------------------------

// MAIN

//--------------------------------------------------------------------

function main()

{

randomize(); // set random seed

 

  Right below that we will add what profession our new player will be. Later on, the player will be able to select his profession, but for now let's just make him an Operations Officer (CBabe) to make sure the code is working correctly. So add this line of code below randomize().

 

randomize(); // set random seed

profession_ID = PROF_OPERATIONS_OFFICER;//PROF_OPERATIONS_OFFICER;

 

  We put in another variable there, let's declare it under Game Variables.

 

var server_says_start = FALSE; // client holds creating entity until server says go

var profession_ID; // players profession ID

 

  Let's go ahead and write a function that will randomly create the new player. Add this script under the display_info() function.

 

str_cat(str_people_connected, str_temp);

 

wait(1);

}

}

 

//--------------------------------------------------------------------

// CREATE PLAYER - create player at random location

//--------------------------------------------------------------------

function create_player()

{

var position_found = FALSE;

 

while (position_found == FALSE)

{

// get random start vector around center of level

vecFrom.x =-400 + random(800);

vecFrom.y =-400 + random(800);

vecFrom.z = 200;

 

vec_set(vecTo,VecFrom);

vecTo.z = -200;

 

trace_mode = IGNORE_SPRITES + IGNORE_PASSENTS + IGNORE_PASSABLE + IGNORE_MODELS + USE_BOX + SCAN_TEXTURE;

TRACE(vecFrom,vecTo);

 

// check for floor texture, if floor create entity

if((str_stri(tex_name,"earthtile") != FALSE)||(str_stri(tex_name,"crator") != FALSE))

{

 

vec_set(temp_loc,vecTo);

temp_loc.z = target.z + 35;

 

if (profession_ID == PROF_OPERATIONS_OFFICER)

{

player = ent_create(str_cbabe,temp_loc,move_officer);&#9;

}

 

position_found = TRUE; // found floor to create player on

}

}

}

 

  First, we declare a local variable called position_found. This is a boolean variable that is set to true when a position for the player is found that is on the floor, not on a wall or something else. We then have a while statement that will continually run until a position on the floor is found.

var position_found = FALSE;

 

while (position_found == FALSE)

{

  Then we create a random position +- 400 from the center of the level on both the x-axis and y-axis. We set z position above floor 200 quants.

// get random start vector around center of level

vecFrom.x =-400 + random(800);

vecFrom.y =-400 + random(800);

vecFrom.z = 200;

  Now we will set our trace down to point at same position except the z position will be 200 quants below floor.

vec_set(vecTo,VecFrom);

vecTo.z = -200;

  Then we do the actual trace from above floor at the random position to below floor at random position checking for the texture of what ever is hit using SCAN_TEXTURE.

trace_mode = IGNORE_SPRITES + IGNORE_PASSENTS + IGNORE_PASSABLE + IGNORE_MODELS + USE_BOX + SCAN_TEXTURE;

TRACE(vecFrom,vecTo);

  Now, that we have the texture of the whatever the trace hit, we can use that to determine if the surface is actually the floor. We have to possible textures that could be considered the floor from our WED Level. The floor itself has the 'earthtile' texture and the crators we placed on floors have the 'crator' texture. So we check if one of these textures was return by the trace and if one was, we know are player is on the floor and not on a wall and we can continue the creation process. If it is not, we skip the creation and position_found remains FALSE and the while loop will try again to find position.

// check for floor texture, if floor create entity

if((str_stri(tex_name,"earthtile") != FALSE)||(str_stri(tex_name,"crator") != FALSE))

{

 

Note: We really didn't have to check for crator texture since we made crators passable, but incase you wanted to place a sprite on surface that wasn't passable but could be walked over I thought I would show that you would have to check for it's texture too.

 

  Next, we go ahead and save the position, setting z to just above where the trace hit the floor (target.z) and our gravity code later will handle keeping player on floor.

vec_set(temp_loc,vecTo);

temp_loc.z = target.z + 35;

  Ok, now we check if our players profession is an Operation Officer, which it is of course, since we hard coded that a bit earlier for testing. If the player's profession is an Operations Officer we go ahead and create his model (CBabe) and state his action, which is move_officer, which will will add here in a second. We save the player's created entity into the player entity pointer, so from now on, he knows which entity is his and then set the entity's profession skill to Operations Officer.

if (profession_ID == PROF_OPERATIONS_OFFICER)

{

player = ent_create(str_cbabe,temp_loc,move_officer);

}

  Now we can let the while loop know we have found a position so it stops searching for one.

position_found = TRUE; // found floor to create player on

  You might have noticed we added some new global vector variables, lets declare them now in declarations under Game Variables.

 

var server_says_start = FALSE; // client holds creating entity until server says go

var profession_ID; // players profession ID

var temp_loc[3]; // temp vector

var vecFrom[3]; // temp vectors

var vecTo[3];

 

   Now let's make the call to the function from main in the if(connection != 1) statement.

if(connection != 1)

{

display_info(); // continually display any information we want to show

 

// if not single player mode, display multiplayer information

if (connection)

{

txt_people_connected.visible = ON;

 }

 

create_player(); // create player

}

 

  We do not need to create a player entity for the dedicated server since no one is playing a player character on it. So we need only to create a player for a single player, client, or host, thus we put it in the if(connection != 1) statement.

  Now let's add the action move_officer script which is actually real short. In this action we are basically just setting the player's profession before calling the actual move_player function. Place this action above the create_player() function.

 

//--------------------------------------------------------------------

// MOVE_OFFICER - action for operations officer

//--------------------------------------------------------------------

action move_officer

{

my.profession = PROF_OPERATIONS_OFFICER;

move_player();

}

 

//--------------------------------------------------------------------

// CREATE PLAYER - create player at random location

 

  Time to move on to the function move_player script. For now we won't add the movement code but just set a random pan. Place this function above the create_player() function.

 

//--------------------------------------------------------------------

// MOVE_PLAYER - Move the player

//--------------------------------------------------------------------

function move_player()

{

my.pan = random(360); // face random direction

 

while(1)

{

wait(1);

}

}

 

//--------------------------------------------------------------------

// CREATE PLAYER - create player at random location

 

  Ok, we created the entity, then we set the player's pan facing a random direction, my.pan = random(360).

  After that we go ahead and do our move code, which is nothing for right now.

while(1)

{

wait(1);

}

 

Note: When you add resources in to SED, you must exit WED if it is open, then restart it for WED to recognize new resources. So if we didn't close WED and re-open it, when we created Resource or Publish executive file, it would have been missing the new resources we added (arrow.pcx, red_guard.mdl, and blue_warlock,mdl), which would result in a run-time error.

 

  Save file in SED, then close and re-open WED file. Now we should be able to do a test run SED. Hit the Test Run icon which should still be set for Host. Since it is possible that the player may be created hidden behind a wall, you may have to exit and run a few times to actually see the player. Here is what you might see.

  Go ahead and create and executive file using Publish or Resource, and copy the multiplayer.cd over the old multiplayer.cd we used last time for our secondary computer, selecting Yes to All. If you copy directly over the last folder we used on the secondary computer, you won't have to re-create the shortcut every time. Now run the program on the primary computer from SED, then double-click the shortcut on the secondary computer. If you try for a while you will get both player's in the view like the picture below.

 

 

Adding Profession Selection Panels

  Now that we have our players so they can be created when the game starts, let's go ahead and change the code a bit so the players can select their profession and have the model for the profession they selected created. First we need to consider how they can select one of the 3 professions possible before they actually join in the game play. What we will do is place 3 profession selection panels on the screen and the game will wait until they have selected a profession before it continues and creates an entity for that player.

  So let's first declare our selection panels' bitmaps and the bitmap for our mouse pointer. Then we will set up our panels. Add the following code in our declarations under Resources.

 

// Fonts

font fnt_century12 = <Centur12.pcx>,16,20; // Century12 font

// Bmaps

bmap pcxArrow = <arrow.pcx>; // pointer arrow

bmap bmpNuclear = <nuclear.bmp>; // nuclear scientiest panel

bmap bmpBiological = <biological.bmp>; // bio scientist panel

bmap bmpOfficer = <officer.bmp>; // op officer panel

bmap pcxTitle = <title.pcx>; // Title panel

bmap pcxSelect = <select.pcx>; // Selection panel

 

  Now that we have our panels bitmaps declared, let's add the panel declarations themselves. Add the following lines of script below our Text declarations.

 

text txt_people_connected

{

pos_x = 0;

pos_y = 65;

layer = 15;

font fnt_century12;

string str_people_connected;

}

 

//--------------------------------------------------------------------

// Panels

//--------------------------------------------------------------------

// Profession selection panels

panel pnlNuclear

{

bmap = bmpNuclear;

layer = 21;

pos_x = 0;

pos_y = 0;

on_click = set_prof_nuclear;

flags = overlay,transparent,refresh;

}

 

panel pnlBiological

{

bmap = bmpBiological;

layer = 21;

pos_x = 0;

pos_y = 0;

on_click = set_prof_biological;

flags = overlay,transparent, refresh;

}

 

panel pnlOfficer

{

bmap = bmpOfficer;

layer = 21;

pos_x = 0;

pos_y = 0;

on_click = set_prof_officer;

flags = overlay,transparent,refresh;

}

 

// panel title

panel pnlTitle

{

bmap = pcxTitle;

layer = 18;

pos_x = 0;

pos_y = 0;

flags = overlay,transparent,refresh;

}

 

// panel select

panel pnlSelect

{

bmap = pcxSelect;

layer = 18;

pos_x = 0;

pos_y = 0;

flags = overlay,transparent,refresh;

}

 

  Ok, this is not a panel tutorial, but here is a short description of what we have done. We have created a panel for each profession. We have located them on the top left-hand side of screen. We use the on_click = function command so that if a panel is left-clicked on by mouse that function will be called. The functions that will be called will set the players profession depending on which panel was clicked on. We also added 2 panels just to make display look better, pnlSelect and pnlTitle.

  You might have noticed that all of the panel positions are equal to zero. We will add a function to set panels postions based on screen size here shortly.

  Before we get to the set profession functions, let's first remove a line of code we had placed in earlier for testing. REMOVE the Red line of code from function main.

 

randomize(); // set random seed

profession_ID = PROF_OPERATIONS_OFFICER;//PROF_OPERATIONS_OFFICER;

 

  Now let's add some code that will hold the program up until a profession has been selected into the main() function.

 

// if not single player mode, display multiplayer information

if (connection)

{

txt_people_connected.visible = ON;

}

 

// wait for a profession to be selected

while(profession_not_set)

{

wait(1);

}

 

  This while-wait loop holds up the program until a player chooses a profession at which time profession_not_set is set to FALSE. Let's declare profession_not_set now that we have put it in. Add it in declarations under Game Variables.

 

var temp_loc[3]; // temp vector

var vecFrom[3]; // temp vectors

var vecTo[3];

var profession_not_set = TRUE; // don't start game until profession set

 

  Now that we have that done, let's go ahead and add the functions which our selection panels will call if they are clicked upon. Add these 3 functions under the create_player() function.

 

position_found = TRUE; // found floor to create player on

}

}

}

 

//--------------------------------------------------------------------

// Function Set_Prof_Nuclear: Player chose nuclear scientist

//--------------------------------------------------------------------

function set_prof_nuclear()

{

profession_ID = PROF_NUCLEAR_SCIENTIST;

profession_not_set = FALSE;

}

 

//--------------------------------------------------------------------

// Function Set_Prof_Biological: Player chose biological scientist

//--------------------------------------------------------------------

function set_prof_biological()

{

profession_ID = PROF_BIOLOGICAL_SCIENTIST;

profession_not_set = FALSE;

}

 

//--------------------------------------------------------------------

// Function Set_Prof_Officer: Player chose operations officer

//--------------------------------------------------------------------

function set_prof_officer()

{

profession_ID = PROF_OPERATIONS_OFFICER;

profession_not_set = FALSE;

}

 

  Now when a player selects a profession panel, the correct set profession function will be called. The function will set the players profession_ID to profession he selected and then set profession_not_set to FALSE, since he has selected a profession. The while-wait loop in main will now be exited and the game will continue.

  We have a few of more things to do before we are done with or selection process. We first need to declare are other 2 professions models we will be using ; red_guard.mdl (Biological Scientist) and blue_warlock.mdl (Nuclear Scientist). So let's declare them in our Resources declarations under Models.

 

//Models

string str_cbabe = <cbabe.mdl>; // CBabe Model

string str_warlock = <blue_warlock.mdl>; // Blue Warlock Model

string str_guard = <red_guard.mdl>; // Red Guard Model

 

  Since we will need to have the mouse turned on for the player to click on panels and then turned off afterwards we need to add some mouse functions for that. I think I actually borrowed these two functions from the templates. See I use the templates sometimes too. They are fairly simple though. Add them directly below the main() function.

 

create_player(); // create player

}

}

 

// Desc: switches the mouse on

function mouse_on()

{

MOUSE_MAP = pcxArrow;

while(MOUSE_MODE > 0)

{

MOUSE_POS.X = POINTER.X;

MOUSE_POS.Y = POINTER.Y;

wait(1); // now move it over the screen

}

}

 

// Desc: switches the mouse off

function mouse_off()

{

MOUSE_MODE = 0;

}

 

  Now we will call to turn on the mouse at the appropriate location in main().

 

// if dedicated server skip these

if(connection != 1)

{

display_info(); // continually display any information we want to show

 

mouse_mode = 2; // mouse with no force changes

mouse_on(); // turn mouse on

 

  We have our panels ready, but we haven't made them visible yet and also haven't added code to make them disappear after the player makes his selection, let's do that now. In main() add the light colored text.

 

// if dedicated server skip these

if(connection != 1)

{

display_info(); // continually display any information we want to show

 

mouse_mode = 2; // mouse with no force changes

mouse_on(); // turn mouse on

 

//display profession selection panels

pnlNuclear.visible = ON;

pnlBiological.visible = ON;

pnlOfficer.visible = ON;

pnlTitle.visible = ON;

pnlSelect.visible = ON;

 

 

  Now, we display the selection panels unless this computer is a dedicated server, which needs no selection panels. Now after selection has been made we need to make the panels invisible and turn off the mouse, so add this script to code in main.

 

// wait for a profession to be selected

 while(profession_not_set)

{

wait(1);

}

 

mouse_off(); // turn mouse off

 

// hide profession selection panels

pnlNuclear.visible = OFF;

pnlBiological.visible = OFF;

pnlOfficer.visible = OFF;

pnlTitle.visible = OFF;

pnlSelect.visible = OFF;

 

create_player(); // create player

 

  We are getting close now. Let's go ahead and set up the panel positions now and move the players_connected text over torwrds right hand side of screen. Add this function under function set_prof_officer().

 

function set_prof_officer()

{

profession_ID = PROF_OPERATIONS_OFFICER;//PROF_OPERATIONS_OFFICER;

profession_not_set = FALSE;

}

 

//--------------------------------------------------------------------

// Function Init_Display: set-up text positions according to screen size

//--------------------------------------------------------------------

function init_display()

{

pnlTitle.pos_y = screen_size.y/2 + 100;

pnlTitle.pos_x = screen_size.x/2 - 150;

pnlTitle.alpha = 75;

pnlSelect.pos_y = screen_size.y/2 - 100;

pnlSelect.pos_x = screen_size.x/2 - 150;

pnlSelect.alpha = 75;

 

pnlBiological.pos_y = screen_size.y/2;

pnlBiological.pos_x = screen_size.x/2 - 300;

pnlBiological.alpha = 75;

pnlNuclear.pos_y = screen_size.y/2;

pnlNuclear.pos_x = screen_size.x/2 - 75;

pnlNuclear.alpha = 75;

pnlOfficer.pos_y = screen_size.y/2;

pnlOfficer.pos_x = screen_size.x/2 + 150;

pnlOfficer.alpha = 75;

&#9;

txt_people_connected.pos_x = screen_size.x - 350;

}

 

  Later we will add more text initializations here based on screen size. This code just places the panels in the correct position on display depending on the screen resolution.

 

Brain Teaser 3: Why don't we just set the text pos_x in the text declaration itself like so?

// text to display the number of people connected to game

text txt_people_connected

{

pos_x = screen_size.x - 350;

  Answer in Appendix V.

 

  Now we add the call to this function in main().

 

// if dedicated server skip these

if (connection != 1)

{

 

init_display(); // initiliaze text positions as necessary

display_info(); // continually display any information we want to show

 

mouse_mode = 2; // mouse with no force changes

 

 One final change and we will be ready to test again. Since we have 3 different models that can be created now, let's add our new models (or professions) into the create_player() function like so.

 

vec_set(temp_loc,vecTo);

temp_loc.z = target.z + 35;

 

// create player's entity depending on profession

if (profession_ID == PROF_NUCLEAR_SCIENTIST)

{

player = ent_create(str_warlock,temp_loc,move_nuclear);

}

 

if (profession_ID == PROF_BIOLOGICAL_SCIENTIST)

{

player = ent_create(str_guard,temp_loc,move_biological);

}

 

if (profession_ID == PROF_OPERATIONS_OFFICER)

{

player = ent_create(str_cbabe,temp_loc,move_officer);

}

 

  Nothing much new here, except we have adding the possibility of creating 2 new models, making sure we set the newly created entity's profession accordingly after creation.

 

  Also, we need to add the new actions for the nuclear and biological scientist right below action move_officer.

 

//--------------------------------------------------------------------

// MOVE_NUCLEAR - action for nuclear scientist

//--------------------------------------------------------------------

action move_nuclear

{

my.profession = PROF_NUCLEAR_SCIENTIST;

move_player();

}

 

//--------------------------------------------------------------------

// MOVE_BIOLOGICAL - action for biological scientist

//--------------------------------------------------------------------

action move_biological

{

my.profession = PROF_BIOLOGICAL_SCIENTIST;

move_player();

}

 

//--------------------------------------------------------------------

// MOVE_OFFICER - action for operations officer

//--------------------------------------------------------------------

action move_officer

 

  That should do it for now for our selection code. Let's give it a test run. Save the multiplayer.wdl in SED. Then click on the Test Run icon. First you will see this screen with the selection panels.

 

  Now you can select any one of the 3 professions by left-clicking on the panel for that profession on the Host. After you do that, the correct model for that profession will be created. Once again, you might have to exit and run the program a few times to get the entity into view.

  Now close WED and re-open it so all of the new resources are added, then create an executive file using Resource or Publish, copy the CD over the old CD on the secondary computer. Test Run the Host from SED or WED, then the Client using the shortcut icon, and after a few tries to get all players in view you might see something similar to this.

 

Note: I will mention this one more time to save you frustration when you get a can't find resource error at run-time while running an executive file. When you add resources in to SED, you must exit WED if it is open, then restart it for WED to recognize new resources. So if we didn't close WED and re-open it, when we created Resource or Publish executive file, it would have been missing the new resources we added (arrow.pcx, red_guard.mdl, and blue_warlock,mdl), which would result in a run-time error.

 

Assigning the Player a Player Number

  We are making progress now. There isn't much left to do for the multiplayer game start. We do however want to assign the player a player number. We could do this in a variety of ways, but since our player is tied specifically to the entity created for him, we will use his entity itself to store his player number.

  Let's look back at the create player code in the create_player() function for a second. Remember where we assigned the player entity pointer to the newly created entity we made for the player. For instance, for a biological scientist selection we had player = ent_create(str_guard,temp_loc,move_biological).

  It is time to explain just what the player pointer is doing here. Player is actually a 3DGS variable that points to the player's entity. Now, since this part of the code is being ran on the Client itself, he is only assigning the player variable locally on the Client or Host. So each of the Client's player pointers will be pointing to his own entity and will not be passed to any other clients or the server. So, anytime a client wants to access his entity, he uses the player variable to do so. For instance, if the Client or Host wanted to know what his entity's health was he could retrieve by player.health as long as the server has been sending the health skill to the Client.

  Now that we understand that, let's set up the player's player number based off of the order in which his player entity was created. We will do this in his move code. We already have a skill set a side for player_number, which is skill1.

  Go ahead and add this code to function move_player().

 

my.pan = random(360); // face random direction

 

// server gives new player's ent his player number

number_of_players += 1; // now that new player's entity has been &

// increment number_of_players

send_var(number_of_players); // send new number_of_players to all clients

my.player_number = number_of_players; // set the player_number skill

send_skill(my.player_number, SEND_ALL); // send player_number skill to all clients

 

  As soon as the new player's entity is created, we increment the number_of_players in game, number_of_players += 1, and then send the new number_of_players to all the Clients, send_var(number_of_players).

 

Note: We don't have to worry about exceeding the maximum players possible how this code is written because that is handled by our people_connected variable and MAX_CONNECTIONS define. Anyway, it would be too late to check if you had exceeded the maximum number of players possible within the action code, because the entity has already been created before you get to this code. There are many other methods possible for assigning player numbers that I will not cover in this tutorial, but as long as you keep the people_connected portion of the code intact, this method should work fine for most applications. Also, for many games, assigning a player a player number really isn't even necessary. It is not even necessary for this example, but I put it in to give an idea of how it could be done. Where you would need a player number assigned to each player, would be if you were accessing arrays, etc, using the player number as the index.

 

  Now, we get the new entity's player_number from the number_of_players in the game. So if this entity was the 5th player entity created, we would have 5 players in game and the entity's player_number would equal 5. An entity's player_number in this code will always be in the range of 1 through the number_of_players.

 

my.player_number = number_of_players;

 

  Then we send the entity's player_number skill to all of the Clients so they all know which player owns this entity, send_skill(my.player_number, SEND_ALL).

 

  Ok, now that we have the player numbers assigned and the number_of _players in game calculated, let's display them on to each players screen. To do this we must first make some declarations. Add these line of code in declarations under Display Strings.

 

string str_temp; // temp string

string str_player_num; // string to hold player number

string str_number_of_players; // string to hold number of players

 

  Next we declare the text to display our new strings under Text declarations.

 

text txt_people_connected

{

pos_x = 0; // set in init_display

pos_y = 65;

layer = 15;

font fnt_century12;

string str_people_connected;

}

// text to display this player's number

text txt_player_number

{

pos_x = 0; // set in init_display

pos_y = 5;

layer = 15;

font fnt_century12;

string str_player_num;

}

 

// text to display the number of players currently in game

text txt_number_of_players

{

pos_x = 0; // set in init_display

pos_y = 35;

layer = 15;

font fnt_century12;

string str_number_of_players;

}

 

  Now that we have our new text declared, let's position on right side of screen so they won't be covered by profession selection panels in game start up. So add this code to init_display().

 

txt_people_connected.pos_x = screen_size.x - 350;

txt_player_number.pos_x = screen_size.x - 350;

txt_number_of_players.pos_x = screen_size.x - 350;

 

 Then we need to make them visible if the game is not running in single player mode in our main() function.

 

// if not single player mode, display multiplayer information

if (connection)

{

txt_people_connected.visible = ON;

txt_number_of_players.visible = ON;

txt_player_number.visible = ON;

 }

 

  After that, we need to add the code to display_info() to continually update the strings to be displayed.

 

while(1)

{

str_cpy(str_people_connected, "People Connected: ");

str_for_num(str_temp, people_connected);

str_cat(str_people_connected, str_temp);

 

str_cpy(str_number_of_players, "Number of Players: ");

str_for_num(str_temp, number_of_players);

str_cat(str_number_of_players, str_temp);

 

if (player != NULL) // player exist

{

str_cpy(str_player_num, "Player Number: ");

str_for_num(str_temp, player.player_number);

str_cat(str_player_num, str_temp);

}

 

  I know what you are thinking, "How did that if (player != NULL) get in there?" Since we are getting this value from a player's entity skill, we need to make sure that the entity exist (has been created and hasn't been killed (removed)). If we don't check for this we will get a run-time error while player is choosing a profession or when he is killed.

  Ok, let's give it a test drive. Save the script in SED and Test Run it. Here is what you will see first before you select a profession.

 

  After you select a profession you will see something like the picture below.

 

  Go ahead and do the create executive, copy over to secondary computer routine and run it again as Host and Client. You will notice that each player's number will be different. Also you will figure out that the Client can choose his player before the Host and thus be player number 1. What we haven't handled yet is player disconnections and adjusting number_of_players and player_number's accordingly. That is covered in ChapterIX.

  Now for the big question, "Will this chapter ever end!"

  Yes, right now, as a matter of fact. Give yourself a "at-a-boy" for a job well done, because this was one of the tougher chapters.

 

Previous Page Contents Next Page