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