Chapter XII
Picking Up and Attaching Items
This is the chapter where the program will actually start looking like a game. This is also the hardest chapter of them all. We will be covering a lot of cool techniques, but it won't be easy. At this point of the tutorial, we ought to be well motivated though, because the end is near. So let's get started, shall we?
Attaching the Weapons to the Players
You might remember from our game documentation that the players will be assigned their weapons at game start. So, since we don't have to worry about picking up the weapons, lets start with them. Let's go ahead and get our weapons declared in Resources in our declarations first.
//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
string str_rifle = <rifle.mdl>; // compression rifle model
string str_nuclear_rod = <nuclearrod.mdl>; // nuclear rod mdl
string str_emulator = <lightgun.mdl>; // photosynthesis emulator model
The rifle(compression rifle) and lightgun(photosynthesis emulator) models were free models I found by searching the forms. Their creators can be found in Appendix II - Contibutions. The nuclear rod was one of my 15 minute jobs.
Anyhow, now that we have our weapon models declared, how in the world are we going to attach them to the players? What we want to do is to attach the weapons to the player's right hand. I am sure there are some good attach scripts out there. I just threw this one together and kept it a simple as possible, like most scripts in the tutorial (the camera for example). I decide to use vec_for_vertex() command to achieve this. The attach code will be simple, so the weapon will not tilt or roll.
We have to start somewhere, so let's start with the biological scientest. Let's find a vertex on his right hand to attach his photosynthesis emulator to. This may take some trial and error to finally get the weapon so it looks decent in the hand. You will just have to play around trying different vertexes until you find one that looks good.
Ok, we are now going to use the Model Editor (MED) for the first time. Open MED and the go File-Open, then find your tutorial work folder and open it, then open Entities folder and select red_guard.mdl. Hit OK. Now we should have our biological scientist model in MED.
Now, find and click on
the Vertex Mode icon.
This will allow us to
select single vertexes. Now click on the Select Icon.
Go ahead and click on some vertexes on the model's right hand. The vertex you select will turn red and the vertex number will show up at the bottom left-hand of window.
The vertex I finally came up with vertex #2 (note: I had to run the attach part of code a few times changing vertex number until it looked pretty good) as shown below.
Now that we have a vertex to attach the weapon model to, let's go ahead and use vec_for_vertex() and to get intial position of the photosynthesis emulator and then create it with ent_create(). So add the following code in function move_player.
my.weapon = WEAPON_PHOTOSYNTHESIS_EMULATOR; // set weapon
you = my; // save me as owner for weapon being created
// Attach correct weapon to player
ent_create(str_emulator, my.X, weapon_local); // created emulator at
// player position locally
proc_local(my,local_activities_bio); // local animation and other activities
First we save the player's entity (MY) to (YOU) so it can be used in the weapons function, you = my; // save me as owner for weapon being created.
Then we get the position to create the weapon using the vertex number we got from MED, vec_for_vertex(temp_vec,my,2); // get position of weapon.
After that, we create the weapon, ent_create(str_emulator, temp_vec, weapon_local); // created emulator.
I suppose I should explain why we have named are weapon function weapon_local() when we are using ent_create(). The reason is, we will soon be changing ent_create() to ent_createlocal(), but I wanted to show you why we are going to do this before we change it. That way you will understand. "Seeing is believing," so they say. To tell you the truth, I have rued the day that I would have to write this chapter.
So let's go ahead and add our weapon function now beneath player_events(). This function will totally change in a moment, after we see what is happening.
send_var(number_of_players); // let everyone know new number of players
ent_remove(me); // remove ent of player that quit
}
}
//--------------------------------------------------------------------
// WEAPON_FUNCTION - moves weapon with player or NPC
//--------------------------------------------------------------------
function weapon_local()
{
my.passable = ON; // make passable so can ignore during player move
while(you)
{
if (you.weapon == WEAPON_PHOTOSYNTHESIS_EMULATOR)
{
vec_for_vertex(my.X,you,2); // get position of weapon
vec_set(my.pan,you.pan); // set pan to owner's pan
my.pan -= 10; // adjust weapon so player can see it better
}
wait(1);
}
remove(my); // remove weapon if owner does not exist any more
}
Here's what this function is doing. It first set's the weapon to passable so it does not interfer with player movement or camera traces. Then as long as the owner, which is the player (You, which we set before function was called) exist, we move the weapon with a vertex on the hand of the owner. We also keep the pan set to the owner's, although I have asdjusted it a bit for this weapon so the player could see his weapon better. You could also adjust the position too if you liked.
Now let's see what the problem is now. Go ahead and Publish or Resource and copy over to secondary computer. Then run the program on both computers making sure you select the Biological Scientist profession for each player since that is the only weapon we have set up so far. Now run the Host and then the Client.
You will notice everything looks fine on the Host, but go over and play around on the Client for a while and you will see something like this.
Notice how the weapon isn't always staying attached to the proper position in the entity's hand on the Client? There is a reason for this. It makes sense if you think about it.
Here is a description of what is happening. In Chapter X we changed our animation so it is now being done locally to lessen network traffic. Now the weapons function code we just added is attaching itself to the server's entity hand. The problem we have is this. Even though the animations being ran on the Host and Client are the same, the current animation frame on the Client may be a bit off of the Host because of the time for the animation_state to be transmitted over the network. That is why, sometimes the weapon looks like it is attached about right on the Client and other times it looks off. The weapon is just moving with the vertex on the server's animation frame.
So the solution to this is to create the weapons locally and attach them to that computer's (Client's) animation instead of the Host's animation. Do you remember back in Chapter X when we put the animation() call into the local activities of the player? We are now going to do something similar here, but instead of making a call, we are actually going to create the weapon locally from within the local activities function with ent_createlocal(). It is important to remember that local_activities_bio() is being ran locally on the Client's computer, not on the server, thus when we run a ent_createlocal() command from within the function it is creating that entity locally and running that entity's function locally too on the Client's computer.
Let's go ahead and add the ent_createlocal() into function local_activities_bio() now.
//--------------------------------------------------------------------
// LOCAL_ACTIVITIES_BIO - performs local ativities for Bio Scientist
//--------------------------------------------------------------------
function local_activities_bio()
{
my.weapon = WEAPON_PHOTOSYNTHESIS_EMULATOR; // set weapon type
you = my; // save MY into YOU because weapon_local() needs to know who owner is
// Attach correct weapon to player
ent_createlocal(str_emulator, my.x, weapon_local); // created emulator at
// player position locally
animate(); // do my animation locally
}
First we saved My into You because our weapon_local() function will be attaching the weapon to a vertex on You, which is the player. My will become a pointer to the weapon in the weapon_local().
Next we create the weapon locally with ent_createlocal(), which calls weapon_local() as it's function.
Now, we need to change the weapon_local() function so it will work locally. Actually, all we have to add is some code for what I believe is a Server Mode bug, this code might be able to be removed sometime after A6.31, but I am not certain at this time. The problem this code is fixing is when in Dedicated Server Mode (connection == 1), when a Client quits game and tries to re-join, a local procedure is still running, even though the entity running this procedure was removed and the client will immediately crash when level is loading.
function weapon_local()
{
my.dynamic = ON; // let server know this entity is not static - do not remove
my.passable = ON; // make passable so can ignore during player move
// temporary code for what I think is a Bug in Server Mode A6.31
// you can remove this in later versions of 3DGS I believe
if(!my.x && !my.y && !my.z)
{
return;
}
while(you)
First we set the my.dynamic = on, which is supposed to let the server know that this is not a static entity (static entities cannot be removed from level).
Note: At the time of the release of this tutorial there is what seems to be a bug when a client exits a game and tries to re-join in Server Mode his old local procedures are still running even though we set his entity's dynamic flag on. This results in the program immediately crashing when the client tries to rejoin. The next lines of script is a temporary solution to this problem and will work. Giorgi3 figured out this work around to the problem. The only thing is you cannot create a player at the position (0,0,0) or the animation will not run. Anyhow, I think this issue will be resolved after A6.31 and this code can be removed. You don't really need to concern yourself with this code at this time, but you may want to remember it is there and comment it out and see if the program runs ok after the next A6 release. Leave the my.dynamic = on in the code, that is what is supposed to make this crash not happen.
// temporary code for what I think is a Bug in Server Mode A6.31
// you can remove this in later versions of 3DGS I believe
if(!my.x && !my.y && !my.z)
{
return;
}
Since we are fixing this problem, we also need to fix it the same way in animate() or it will crash there too under the same circumstances.
function animate()
{
my.dynamic = ON; // let server know this entity is not static - do not remove
proc_late(); // make sure forces set before we run animation
// temporary code for what I think is a Bug in Server Mode A6.31
// you can remove this in later versions of 3DGS I believe
if(!my.x && !my.y && !my.z)
{
return;
}
while(my)
Now all there is left to do is to create the entity that the Server creates locally on itself too so it doesn't make clones of the weapons and send them to all the Clients since the Clients are creating their weapons locally.
So, in function move_player Change this.
my.weapon = WEAPON_PHOTOSYNTHESIS_EMULATOR; // set wepaon
you = my; // save me as owner for weapon being created
// Attach correct weapon to player
ent_create(str_emulator, my.X, weapon_local); // created emulator at
To this.
my.weapon = WEAPON_PHOTOSYNTHESIS_EMULATOR; // set wepaon
you = my; // save me as owner for weapon being created
// Attach correct weapon to player
ent_createlocal(str_emulator, my.X, weapon_local); // created emulator at
Now if you run the program selecting the biological scientist as the profession for Host and Client, you will see that the weapons are now attached correctly to the local animation.
Let's go ahead and get all of the professions set up now.
In action main under the if (my.profession == PROF_NUCLEAR_SCIENTIST) statement add this code.
send_skill(my.health, SEND_VEC+SEND_ALL);
my.weapon = WEAPON_NUCLEAR_ROD; // set weapon type
you = my; // save me as owner for weapon being created
// Attach correct weapon to player
ent_createlocal(str_nuclear_rod, my.X, weapon_local); // created rod at
// player position locally
proc_local(my,local_activities_nuclear); // local animation and other activities
In action main under the if (my.profession == PROF_OPERATION_OFFICER) statement add this code.
send_skill(my.health, SEND_VEC+SEND_ALL);
my.weapon = WEAPON_COMPRESSION_RIFLE; // set weapon type
you = my; // save me as owner for weapon being created
// Attach correct weapon to player
ent_createlocal(str_rifle, my.X, weapon_local); // created rifle at
// player position locally
proc_local(my,local_activities_officer); // local animation and other activities
Now in function local_activities_nuclear() add this code.
//--------------------------------------------------------------------
// LOCAL_ACTIVITIES_NUCLEAR - performs local ativities for Nuke Scientist
//--------------------------------------------------------------------
function local_activities_nuclear()
{
my.weapon = WEAPON_NUCLEAR_ROD; // set weapon type
you = my; // save MY into YOU because weapon_local() needs to know who owner is
// Attach correct weapon to player
ent_createlocal(str_nuclear_rod, my.x, weapon_local); // created emulator at
// player position locally
animate(); // do my animation locally
}
In local_activities_officer() add this code.
function local_activities_officer()
{
my.weapon = WEAPON_COMPRESSION_RIFLE; // set weapon type
you = my; // save MY into YOU because weapon_local() needs to know who owner is
// Attach correct weapon to player
ent_createlocal(str_rifle, my.x, weapon_local); // created emulator at
// player position locally
animate(); // do my animation locally
}
Save the multiplayer.wdl from SED. Run MED and open up the blue_warlock.mdl(nuclera scientest), we need find a vertex to attach the nuclear rod too like we did earlier with the red_guard.mdl (biological scientist) earlier in chapter.
Using the same techniques as earlier I cam up with vertex# 9 as shown below.
Since we have MED open, let's get a vertex for Cbabe.mdl (operations officer) to attach the rifle to. I chose vertex # 354 as shown below.
Now that we know our vertex numbers, let add the code to attach the weapons to these vertexes in function weapon_local().
if (you.weapon == WEAPON_PHOTOSYNTHESIS_EMULATOR)
{
vec_for_vertex(my.X,you,2); // get position of weapon
vec_set(my.pan,you.pan); // set pan to owner's pan
my.pan -= 10; // adjust weapon so player can see it better
}
if (you.weapon == WEAPON_NUCLEAR_ROD)
{
vec_for_vertex(my.X,you,9); // get position of weapon
vec_set(my.pan,you.pan); // set pan to owner's pan
}
if (you.weapon == WEAPON_COMPRESSION_RIFLE)
{
vec_for_vertex(my.X,you,354); // get position of weapon
vec_set(my.pan,you.pan); // set pan to owner's pan
}
We are moving right along here, aren't we? Ok go ahead and save and do a test run now as Host and Client. We can now select any profession and the correct weapon will be attached locally like so.
Life is good! Life is grand! Let's get on to picking up power-ups now.
Picking Up Items from Level
As stated in our game documentation in Chapter II, we will have 4 power-ups that can be pick up from level.
Lunar_Candy:
Lunar Candy heals a player. Adds Health when picked up to Max_Health.
Ammunition_Packs:
Used_Nuclear_Fuel: Ammunition for Nuclear Rod.
Photosynthesis_Jelly: Ammunition for Photosythesis Emulator.
SR-45b Ammunition Cartridge: Ammunition for Compression Rifle.
First let's get the models declared in declarations.
string str_rifle = <rifle.mdl>; // compression rifle model
string str_nuclear_rod = <nuclearrod.mdl>; // nuclear rod mdl
string str_emulator = <lightgun.mdl>; // photosynthesis emulator model
string str_ammo_cartridge = <ammocartridge.mdl>; //ammo cartridge model
string str_nuclear_fuel = <nuclearfuel.mdl>; // nuclear fuel model
string str_photo_jelly = <photojelly.mdl>; // photosynthesis jelly model
string str_lunar_candy = <lunarcandy.mdl>; // lunar candy model
Next, let's define our power-up types in declarations.
// Animation Speeds
define PLAYER_ANIM_WALK_SPEED, 6; // animation speed for walk
define PLAYER_ANIM_RUN_SPEED, 8; // animation speed for run
define PLAYER_ANIM_BLEND_SPEED, 10; // speed for blend
// Powerup Types
define POWER_UP_USED_NUCLEAR_FUEL ,1; // Ammunition for Nuclear Rod.
define POWER_UP_PHOTOSYNTHESIS_JELLY, 2; // Ammunition for Photosythesis Emulator.
define POWER_UP_AMMUNITION_CARTRIDGE, 3; // Ammunition for Compression Rifle.
define POWER_UP_LUNAR_CANDY, 4; // lunar candy power-up
Right beneath that, lets add a define for the maximum number of any power-up model that can be present in the level at one time. So there can be a maximum of 10 ammunition cartridges models, 10 used nuclear fuel models, 10 photosynthesis jelly models, and 10 lunar candy models present in the level at one tim.
define POWER_UP_AMMUNITION_CARTRIDGE, 3; // Ammunition for Compression Rifle.
define POWER_UP_LUNAR_CANDY, 4; // lunar candy power-up
// Power-up defines
define MAX_POWERUP_TYPE_IN_LEVEL, 10; // Maximum number of any power-up type
Now, let's create a function that will create power-ups randomly in level over time up to the maximum number of possible power-ups. This function will be called at game start from server or if single player and will contually check for power-up spawns. Place this function below chat_entry().
str_cpy(strSendChat,""); // clear the strings for client and
str_cpy(strChatEntry,""); // server called functions
}
}
//--------------------------------------------------------------------
// Function Spawn_Power_Ups: randomly create power-ups
//--------------------------------------------------------------------
function spawn_power_ups()
{
var random_number;
while(1)
{
randomize();
random_number = int(random(1000)) + 1;
// the more players in game, the more likely a power-up is spawned
if (random_number > (999 - number_of_players))
{
create_power_up(); // place a power up into level
}
wait(1);
}
}
Now, right above that function let's add the function that figures out what to spawn.
//--------------------------------------------------------------------
// Function Create_Power_Up: create a random power-up
//--------------------------------------------------------------------
function create_power_up()
{
var random_number;
var position_found = FALSE; // have we found the floor?
// while floor not found try new random vector for creation
while (position_found == FALSE)
{
// get random start place in middle of level
vecFrom.x =-550 + random(1100);
vecFrom.y =-550 + random(1100);
vecFrom.z = 200;
// get where to trace to
vec_set(vecTo,VecFrom);
vecTo.z = -200;
// trace checking for texture hit
trace_mode = IGNORE_SPRITES + IGNORE_PASSENTS + IGNORE_PASSABLE + IGNORE_MODELS + SCAN_TEXTURE + USE_BOX;
TRACE(vecFrom,vecTo);
// if we hit floor's texture than place power-up there
if(str_stri(tex_name,"earthtile") != FALSE)
{
position_found = TRUE; // we have now found the floor so set power-up
//type if that type not maxed
// set origin position
vec_set(temp_loc,vecTo);
temp_loc.z = 25;
// get random power-up type
random_number = int(random(10)) + 1;
// 7 - 10 = Lunar_Candy - if level not full of them, create 1
if ((random_number >= 7) && (lunar_candy_count <= 10))
{
ptrTempEnt = ent_create(str_lunar_candy,temp_loc,candy_function);
ptrTempEnt.power_up_type = POWER_UP_LUNAR_CANDY;
lunar_candy_count += 1; // increment lunar candies in level
goto(created_power_up);
}
// 5 - 6 = Photosynthesis Jelly - if level not full of them, create 1
if ((random_number >= 5) && (photo_jelly_count <= 10))
{
ptrTempEnt = ent_create(str_photo_jelly,temp_loc,ammo_function);
ptrTempEnt.power_up_type = POWER_UP_PHOTOSYNTHESIS_JELLY;
photo_jelly_count += 1; // increment photo jellies in level
goto(created_power_up);
}
// 3 - 4 = Nuclear Rod - if level not full of them, create 1
if ((random_number >= 3) && (nuclear_fuel_count <= 10))
{
ptrTempEnt = ent_create(str_nuclear_fuel,temp_loc,ammo_function);
ptrTempEnt.power_up_type = POWER_UP_USED_NUCLEAR_FUEL;
nuclear_fuel_count += 1; // increment nuclear fuels in level
goto(created_power_up);
}
// 1 - 2 = Ammo Catridge - if level not full of them, create 1
if ((random_number >= 1) && (ammo_cartridge_count <= 10))
{
ptrTempEnt = ent_create(str_ammo_cartridge,temp_loc,ammo_function);
ptrTempEnt.power_up_type = POWER_UP_AMMUNITION_CARTRIDGE;
ammo_cartridge_count += 1; // increment ammo cartridges in level
}
created_power_up:
}
}
}
//--------------------------------------------------------------------
// Function Spawn_Power_Ups: randomly create power-ups
//--------------------------------------------------------------------
Ok, there will be no long drawn out explaination here. This function is called randomly from spawn_power_ups() functunction when a power up need to be created. If first finds a position on the floor. Once it finds a position it then choses once of our 4 possible power-ups randomly. If the number of that power-up is not maxed-out already it will create that power-up incrementing the count of that power-up type currently in level, otherwise it won't create a power-up at all.
We added some new variables, let's declare them now.
var profession_not_set = TRUE; // don't start game until profession set
var force[3]; // temp forces
var nuclear_fuel_count; // number of nuclear fuel power ups in level
var ammo_cartridge_count; // number ammo cartridges in level
var photo_jelly_count; // number of photo jellies in level
var lunar_candy_count; // number of lunar candies in level
We put in a new skill to hold the power-up type of this entity. Let's add that now in Skill Definitions.
define prev_anim_state, skill14; // previous animation state for blending
define weapon, skill15; // weapon ID this ent wields
define power_up_type, skill16; // power up type of this ent if a power up
We also added a temporary entity pointer that we need to declare under Strings.
string strChat7[100];
string strChat8[100];
string strChatEntry[80]; // current chat entry
string strSendChat[100]; // chat to send
//--------------------------------------------------------------------
// Entity Pointers
//--------------------------------------------------------------------
entity* ptrTempEnt; // temp pointer to a entity
You will notice we also added 2 new functions that will be called on the power-up creation. One for the ammunitions and one for lunar candy.
Let's go ahead and add these 2 functions above the create_power_up() function.
//--------------------------------------------------------------------
// Function Ammo_Function: set-up ammunition power-ups
//--------------------------------------------------------------------
function ammo_function()
{
//*** A6.3 feature my.floor_dist to scan the floor and to set entity onto the floor
if(MY.FLOOR_DIST > (MY.MAX_Z - MY.MIN_Z)/2)
{
MY.Z -= MY.FLOOR_DIST - (MY.MAX_Z - MY.MIN_Z)/2;
}
/* A5 scan floor;
// scan for floor if we are moving
if (my.force_x || my.force_y || my.force_z)
{
//Scan floor pre A6.30
trace_mode = IGNORE_SPRITES + IGNORE_PASSENTS + IGNORE_PASSABLE + IGNORE_MODELS + USE_BOX;
vec_set(vecFrom,my.X);
vec_set(vecTo,my.X);
vecTo.z -= 400;
RESULT = TRACE(vecFrom,vecTo);
my.Z = TARGET.Z + ((my.MAX_Z - my.MIN_Z)/2);//Set to the floor
}
*/
my.z -= 30; // model box adjustment for ammo
}
//--------------------------------------------------------------------
// Function Candy_Function: set-up lunar candy power-ups
//--------------------------------------------------------------------
function candy_function()
{
//*** A6.3 feature my.floor_dist to scan the floor and to set entity onto the floor
if(MY.FLOOR_DIST > (MY.MAX_Z - MY.MIN_Z)/2)
{
MY.Z -= MY.FLOOR_DIST - (MY.MAX_Z - MY.MIN_Z)/2;
}
/* A5 scan floor;
// scan for floor if we are moving
if (my.force_x || my.force_y || my.force_z)
{
//Scan floor pre A6.30
trace_mode = IGNORE_SPRITES + IGNORE_PASSENTS + IGNORE_PASSABLE + IGNORE_MODELS + USE_BOX;
vec_set(vecFrom,my.X);
vec_set(vecTo,my.X);
vecTo.z -= 400;
RESULT = TRACE(vecFrom,vecTo);
my.Z = TARGET.Z + ((my.MAX_Z - my.MIN_Z)/2);//Set to the floor
}
*/
my.z -= 25;
}
//--------------------------------------------------------------------
// Function Create_Power_Up: create a random power-up
//--------------------------------------------------------------------
Next. let's add the call to the function spawn_power_ups() from our main() function.
level_load(world_str); // load level
sleep(.5); // make sure level is loaded
// had to place check for power ups here incase server doesn't choose professiion
// until after clients choose theirs
// if not client, create random power-ups (if server, host, or single-player)
if(connection != 2)
{
spawn_power_ups(); // continually create power-ups
}
OK, we can do a test run as Host now and watch the the power-ups be randomly spawned over time. We can't pick them up yet because we haven't added that code yet, but we can test to make sure they are spawning.
Now, let's get the power-ups so we can pick them up add do the proper action when we do so. Let's go ahead and do the hardest power-up's first, which are the ammunition power-ups. In declarations under skill definitions add this line of code so player knows how much ammunition he has left.
define prev_anim_state, skill14; // previous animation state for blending
define weapon, skill15; // weapon ID this ent wields
define power_up_type, skill16; // power up type of this ent if a power up
define ammunition_remaining, skill17; // ammunition entity has left
Let's also add a define for the number of rounds of ammunition each player starts game with and the max ammunition that can be carried..
// Power-up defines
define MAX_POWERUP_TYPE_IN_LEVEL, 10; // Maximum number of any power-up type
// Ammunition Misc
define AMMO_GAME_START, 10; // The number of rounds each player starts game with
define AMMO_MAX, 50; // maximum ammunition a entity can have // Ammunition at Game Start
Now in function move_player let's set the ammunition_remaining skill to are game start ammunition.
my.nosend_frame = ON; // don't send animation
my.pan = random(360); // face random direction
my.ammunition_remaining = AMMO_GAME_START; // rounds of ammunition at creation
Next, in the function ammo_function(), let's enable collison and set our event function for when a ammunition power-up is collided with.
function ammo_function()
{
my.ENABLE_IMPACT = ON; // event for hit by another moving entity
my.EVENT = ammo_event; // event function
Now let's add the ammo_event() function above the ammo_function(), which should give us a migrane as we go through the code.
//--------------------------------------------------------------------
// Function Ammo_Event: ammunition power-up touched, see if it needs
// picked up
//--------------------------------------------------------------------
function ammo_event()
{
// if player holding rifle and ammo is ammunition cartridge
// player picks it up
if ((you.weapon == WEAPON_COMPRESSION_RIFLE) && (my.power_up_type == POWER_UP_AMMUNITION_CARTRIDGE))
{
// only can pick-up if ammo not full
if (you.ammunition_remaining < AMMO_MAX)
{
// add up to 15 rounds of ammo
you.ammunition_remaining += int(random(15)) + 1;
// if over max ammo, set to max ammo
if (you.ammunition_remaining > AMMO_MAX)
{
you.ammunition_remaining = AMMO_MAX;
}
// play power-up sound at ent
ent_playsound (you, snd_power_up, 60);
// send ammo remaining to clients
send_skill(you.ammunition_remaining, 0);
ammo_cartridge_count -= 1; // 1 less cartridge in level
ent_remove(my); // remove ammo
}
}
else
{
// if player holding nuclear rod and ammo is nuclear fuel
/// player picks it up
if ((you.weapon == WEAPON_NUCLEAR_ROD) && (my.power_up_type == POWER_UP_USED_NUCLEAR_FUEL))
{
// only can pick-up if ammo not full
if (you.ammunition_remaining < AMMO_MAX)
{
// add up to 15 rounds of ammo
you.ammunition_remaining += int(random(15)) + 1;
// if over max ammo, set to max ammo
if (you.ammunition_remaining > AMMO_MAX)
{
you.ammunition_remaining = AMMO_MAX;
}
// play power-up sound at ent
ent_playsound (you, snd_power_up, 60);
// send ammo remaining to clients
send_skill(you.ammunition_remaining, 0);
nuclear_fuel_count -= 1; // 1 less nuke fuel in level
ent_remove(my); // remove ammo
}
}
else
{
if ((you.weapon == WEAPON_PHOTOSYNTHESIS_EMULATOR) && (my.power_up_type == POWER_UP_PHOTOSYNTHESIS_JELLY))
{
// only can pick-up if ammo not full
if (you.ammunition_remaining < AMMO_MAX)
{
you.ammunition_remaining += int(random(15)) + 1;
// if over max ammo, set to max ammo
if (you.ammunition_remaining > AMMO_MAX)
{
you.ammunition_remaining = AMMO_MAX;
}
// play power-up sound at ent
ent_playsound (you, snd_power_up, 60);
// send ammo remaining to clients
send_skill(you.ammunition_remaining, 0);
photo_jelly_count -= 1; // 1 less jelly in level
ent_remove(my); // remove ammo
}
}
}
}
}
//--------------------------------------------------------------------
// Function Ammo_Function: set-up ammunition power-ups
//--------------------------------------------------------------------
I am just going to explain what is happenning in the first if stament since all the rest of them are the same except they are setting up different ammunition types. We must remember that the you variable has been set to the entity that collided with the ammunition pack. Ok now we can go through the code.
First we check if the ammunition_type of this ammo pack that was collided with is a AMMO_AMMUNITION_CARTRIDGE and if the colliding entity's weapon is a WEAPON_COMPRESSION_RIFLE. Since a compression rifle uses a ammunition cartridge, if this if statement is true we can go ahead and add ammunition to the colliding entity's ammunition_remaining.
// if player holding rifle and ammo is ammunition cartridge
// player picks it up
if ((you.weapon == WEAPON_COMPRESSION_RIFLE) && (my.ammo_type == AMMO_AMMUNITION_CARTRIDGE))
{
Ok, so if a the entity that collided with the ammo pack is carrying a compression rifle and the ammunition is a ammunition cartridge we can add the ammunition catridge rounds into the player's ammunition remaining.
Next we make sure the player's ammunition_remaining is not maxed out. If it is we do not allow him to pick up the ammo pack.
// only can pick-up if ammo not full
if (you.ammunition_remaining < AMMO_MAX)
{
If his ammunition is not maxed, then we add some random ammunition and if we go over the max ammunition allowed then we set the ammunition_remaining to AMMO_MAX.
// add up to 15 rounds of ammo
you.ammunition_remaining += int(random(15)) + 1;
// if over max ammo, set to max ammo
if (you.ammunition_remaining > AMMO_MAX)
{
you.ammunition_remaining = AMMO_MAX;
}
Then we use ent_playsound() to play the power-up pick up sound at the player's position.
// play power-up sound at ent
ent_playsound (you, snd_power_up, 60);
After that, we send the player's ammunition_remaining skill to the client that we added the ammunition to, so he can display his ammo by using send_skill(entity.skill, 0). Then we decrement the number of ammunition cartridge power-ups in the level. and then remove the ammunition cartridge model from the level.
// send ammo remaining to clients
send_skill(you.ammunition_remaining, 0);
ammo_cartridge_count -= 1; // 1 less cartridge in level
ent_remove(my); // remove ammo
Then, if the first if statement wasn't true we check for the other weapons and ammunition types and then do the same kind of action for them if they match.
Ok, let's declare our power-up sound since we have it in the code now. Actually, let's go ahead and define all of our sounds since we be using them all soon. In declarations under Resources add these sound declarations.
// Fonts
font fnt_century12 = <Centur12.pcx>,16,20; // Century12 font
// Sounds
sound snd_radiation_beam = <radiationbeam.wav>;
sound snd_emulator = <emulator.wav>;
sound snd_rifle = <rifle.wav>;
sound snd_damage = <damage.wav>;
sound snd_no_damage = <nodamage.wav>;
sound snd_power_up = <powerup.wav>;
We are not quite done yet. Lets add a string and some text to display the player's ammuntion_remaining and his AMMO_MAX so he knows what he has left. In declarations under Display Strings add this.
string str_number_of_players; // string to hold number of players
string str_player_name; // player name for text display
string str_ammo_remaining; // ammunition count of this player
Then in declarations under Text add this.
// text to display this player's name
text txt_player_name
{
pos_x = 5;
pos_y = 5;
layer = 15;
font fnt_century12;
string str_player_name;
}
// text to display this player's ammunition remaining
text txt_ammo_remaining
{
pos_x = 5;
pos_y = 95;
layer = 15;
font fnt_century12;
string str_ammo_remaining;
}
And then in the display_info() function add this code in the if(player != NULL) statement.
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);
str_cpy(str_ammo_remaining, "Ammunition Remaining: ");
str_for_num(str_temp, player.ammunition_remaining);
str_cat(str_ammo_remaining, str_temp);
str_cat(str_ammo_remaining, "/");
str_for_num(str_temp, AMMO_MAX);
str_cat(str_ammo_remaining, str_temp);
}
Now we need to add the turn on and turn off script for the text in function main().
mouse_mode = 2; // mouse with no force changes
mouse_on(); // turn mouse on
// turn off all text not needed for profession selection
txt_ammo_remaining.visible = OFF;
And...
// wait for this player's entity to be created
while(!player)
{
wait(1);
}
// display player specific text
txt_ammo_remaining.visible = ON;
And last but not least, let's make some changes that will make un-pickable power-ups passable depending on the weapon carried by the player. I didn't original have this code in the tutorial, but after some test some people suggested that non-usable power-ups should be passable so I added it. But this one you will have to do your homework on if you want to figure it out. In function move_player remove these lines of code.
// use time & speed_forwards_factor
force.x = my.force_x * my.speed * time;
force.y = 0;
force.z = 0;
// A5 move
move_mode = glide + ignore_passable;
ent_move(force.x,nullvector);
//A6.3 move the player, c_move bugged at time of release so using ent_move instead
//c_move(my, force.x, nullvector, glide + ignore_passable);
And replace it with this wonderous script. Beware what you wish for.
// use time & speed_forwards_factor
force.x = my.force_x * my.speed * time;
force.y = 0;
force.z = 0;
trace_vecTo.x = my.x + (force.x*2) * cos(my.pan);
trace_vecTo.y = my.y + (force.x*2) * sin(my.pan);
trace_vecTo.z = my.z;
//A5 trace
trace_mode = ignore_passable + ignore_passents + ignore_me + use_box;
trace(my.X, trace_vecTo);
//A6.3 trace
//c_trace(my.X, trace_vecTo, ignore_passable + ignore_passents + ignore_me + use_box);
// if hit something check if it should be passable or not
if(you)
{
if((my.ammunition_remaining == AMMO_MAX)&&(my.health == my.max_health))
{
// A5 move
move_mode = glide + ignore_passable + ignore_passents + ignore_you;
ent_move(force.x,nullvector);
//A6.3 move the player, c_move bugged at time of release so using ent_move instead
//c_move(my, force.x, nullvector, glide + ignore_passable + ignore_passents + ignore_you);
}
else
{
if(you.power_up_type == POWER_UP_LUNAR_CANDY)
{
if(my.health < my.max_health)
{
// A5 move
move_mode = glide + ignore_passable + ignore_passents;
ent_move(force.x,nullvector);
//A6.3 move the player, c_move bugged at time of release so using ent_move instead
//c_move(my, force.x, nullvector, glide + ignore_passable + ignore_passents);
}
else
{
// A5 move
move_mode = glide + ignore_passable + ignore_passents + ignore_you;
ent_move(force.x,nullvector);
// A6.3 move the player, c_move bugged at time of release so using ent_move instead
//c_move(my, force.x, nullvector, glide + ignore_passable + ignore_passents + ignore_you);
}
}
else // power up is ammo
{
if ((((my.weapon == WEAPON_COMPRESSION_RIFLE) && (you.power_up_type == POWER_UP_AMMUNITION_CARTRIDGE))
|| ((my.weapon == WEAPON_NUCLEAR_ROD) && (you.power_up_type == POWER_UP_USED_NUCLEAR_FUEL))
|| ((my.weapon == WEAPON_PHOTOSYNTHESIS_EMULATOR) && (you.power_up_type == POWER_UP_PHOTOSYNTHESIS_JELLY)))
&& (my.ammunition_remaining < AMMO_MAX))
{
// A5 move
move_mode = glide + ignore_passable + ignore_passents;
ent_move(force.x,nullvector);
//A6.3 move the player, c_move bugged at time of release so using ent_move instead
//c_move(my, force.x, nullvector, glide + ignore_passable + ignore_passents);
}
else
{
// A5 move
move_mode = glide + ignore_passable + ignore_passents + ignore_you;
ent_move(force.x,nullvector);
// A6.3 move the player, c_move bugged at time of release so using ent_move instead
//c_move(my, force.x, nullvector, glide + ignore_passable + ignore_passents + ignore_you);
}
}
}
}
else
{
// A5 move
move_mode = glide + ignore_passable + ignore_passents;
ent_move(force.x,nullvector);
//A6.3 move the player, c_move bugged at time of release so using ent_move instead
//c_move(my, force.x, nullvector, glide + ignore_passable + ignore_passents);
}
Yeah baby! That was a fun one. That ought to get the old brain juices flowing.
We added a couple of variables in there, so let's add them now to function move_player.
function move_player()
{
var trace_vecFrom[3];
var trace_vecTo[3];
You may be asking yourself right about now, "Did I really want to make a multiplayer game?"
Well, of course you did. You just might not have realized the complexity of it all. Anyhow, let's give it a test run on with a Host and Client and make sure all is working well. You should be able to pick up the right ammunition packs for your player's profession now up to the maximum allowed ammo. You should now see something like this.
Darn! We are not done with this chapter just yet. We still need to add the lunar candy power-up's code for health. I am not going to explain everything we are adding here, because it's basically the same as what we just did for the ammunition packs. It is actually much easier since we don't have to compare the weapon type to the ammunition type.
First, in the candy_function() add the event code.
function candy_function()
{
my.ENABLE_IMPACT = ON; // event for hit by another moving entity
my.EVENT = candy_event; // event function
Then above the candy_function() add the candy_event().
//--------------------------------------------------------------------
// Function Candy_Event: lunar_candy(health) power-up touched,add some health
//--------------------------------------------------------------------
function candy_event()
{
// if touched lunar candy and not at max health
if (you.health < you.max_health)
{
// add health
you.health += int(random(10) + you.max_health/10);
// if over max health, set to max health
if(you.health > you.max_health)
{
you.health = you.max_health;
}
// play power-up sound ant ent
ent_playsound (you, snd_power_up, 60);
send_skill(you.health,SEND_ALL); // send new health to clients
lunar_candy_count -= 1; // 1 less lunar candy in level
ent_remove(my); // remove lunar candy
}
}
//--------------------------------------------------------------------
// Function Candy_Function: set-up lunar candy power-ups
//--------------------------------------------------------------------
Next, let's add the string declarations.
string str_player_name; // player name for text display
string str_ammo_remaining; // ammunition count of this player
string str_health; // player's health string
Then the text declaration.
// text to display this player's ammunition remaining
text txt_ammo_remaining
{
pos_x = 5;
pos_y = 95;
layer = 15;
font fnt_century12;
string str_ammo_remaining;
}
// text to display this player's health
text txt_health
{
pos_x = 5;
pos_y = 65;
layer = 15;
font fnt_century12;
string str_health;
}
Next in display_info() add this code.
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);
str_cpy(str_health, "Health: ");
str_for_num(str_temp, int(player.health));
str_cat(str_health, str_temp);
str_cat(str_health, "/");
str_for_num(str_temp, player.max_health);
str_cat(str_health, str_temp);
In function main() we need to add
// turn off all text not needed for profession selection
txt_health.visible = OFF;
txt_ammo_remaining.visible = OFF;
and
// display player specific text
txt_health.visible = ON;
txt_ammo_remaining.visible = ON;
After that, we need to take a break because we are finally done with this chapter. Whew! I thought it would never end.
If you run the program now each player's health and max health should be displayed, but you cannot pick up the lunar candy yet because all of the the player's health skills are at max. The player's will need to do some damage to each other before they can pick up the candy, which will be in the next chapter. Hooray!
Previous Page Contents Next Page