Chapter XIII

Using Items and Attacking

 

  Alrighty then, let's have some fun now. In this chapter we will actually get to the point where we do damage, kill each other, and keep track of our Lunar Psychosis Rating (score). After this chapter we cruise to the finish. So let's dive right in.

 

Attacking and Doing Damage

 What the heck, let's go ahead and get everything declared right from the start so we don't have to bother ourselves with all of that this chapter. In declarations under Models add this.

 

string str_photo_jelly = <photojelly.mdl>; // photosynthesis jelly model

string str_lunar_candy = <lunarcandy.mdl>; // lunar candy model

string str_bullet = <bullet.mdl>; // bullet model

string str_petri_dish = <petridish.mdl>; // petri_dish

string str_radiation_beam = <radiationbeam.mdl>; // radiation beam model

 

  Under Bmaps add.

 

bmap pcxChat = <Chat.pcx>; // Chat Panel

bmap spark_map,<particle.pcx>; // bitmap used for sparks

bmap* scatter_map = spark_map;

 

  Under Defines add.

 

// Power-up defines

define MAX_POWERUP_TYPE_IN_LEVEL, 10; // Maximum number of any power-up type

 

// Weapon recharge

define WEAPON_RECHARGE_DELAY, 25; // reload time in frames

 

// Projectile speed

define PROJECTILE_SPEED, 30; // speed of projectiles

 

// Player's grace time in frames (cannot be hit upon creation)

define PLAYER_GRACE_TIME, 600;

 

// Ammunition life_spans

define AMMO_LIFESPAN_NUCLEAR, 200; // lifespan of raditiaon beam

define AMMO_LIFESPAN_EMULATOR, 110; // lifespan of petri-dish

define AMMO_LIFESPAN_RIFLE, 135; // lifespan of bullet

 

// Local Effects

define EFFECT_HIT_EXPLOSION, 1; // big explosion on hit

define EFFECT_RICOCHET_EXPLOSION, 2; // little explosion on ricochet

 

  Don't let this scare you, but add the following skills under Skill Definitions.

 

define ammunition_remaining, skill17; // ammunition entity has left

define damage_die_1, skill18; // random damage of die 1

define damage_die_2, skill19; // random damage of die2

define firing_weapon, skill20; // TRUE if ent firing weapon

define life_span, skill21; // projectile life_span

define animation_attack_life, skill22; // how long to run attack anim on fire

define grace_time, skill23; // time left in ent's grace period

define explosion_normal_x, skill24; // normal vec used by projectile for effect

define explosion_normal_y, skill25;

define explosion_normal_z, skill26;

define owned_by_hdl, skill27; // owner of this ent (used for projectile)

 

  We also need to add a few variables under Game Variables.

 

var photo_jelly_count; // number of photo jellies in level

var lunar_candy_count; // number of lunar candies in level

var server_restart; // server died, server go back to profession selection

var client_restart; // client died, client go back to profession selection

var start_cam_pos[3]; // on death revert back to start cam position

var start_cam_pan[3];

var weapon_recharge_count = WEAPON_RECHARGE_DELAY; // reload time

var send_server_player; // tells server to send his player ent to all client

var effect_pos[3]; // position to do local effect

var effect_normal[3]; // normal for local explosion

var do_effect_type; // type of effect to do locally

var player_number_quit; // number of player who quit or was killed

 

  In Display Strings add.

 

string str_health; // player's health string

string str_lunar_psychosis_rating; // score

string str_latency;

string str_bps;

 

  Also add this string directly under that.

 

string str_latency;

string str_bps;

 

// message strings

string str_status_message; // tells player game status, Loading & Grace Period

 

  Add these entity pointers under, you guessed it, Entity Pointers.

 

entity* ptrTempEnt; // temp pointer to a entity

entity* ptrOwner; // pointer to owner of an entity

entity* ptrTempOwner; // temp pointer to owner of an entity

 

  Under Text add these.

 

// text to display this player's health

text txt_health

{

pos_x = 5;

pos_y = 65;

layer = 15;

font fnt_century12;

string str_health;

}

 

// text that tells player game status

text txt_status_message

{

pos_x = 0; // set in init_display

pos_y = 0; // set in init_display

layer = 15;

font fnt_century12;

string str_status_message;

}

 

// text to display this player's score

text txt_lunar_psychosis_rating

{

pos_x = 5;

pos_y = 35;

layer = 15;

font fnt_century12;

string str_lunar_psychosis_rating;

}

 

// text for latency

text txt_latency

{

pos_x = 0; // set in init_display()

pos_y = 95;

layer = 15;

font fnt_century12;

string str_latency;

}

 

// text for bytes per second

text txt_bps

{

pos_x = 0; // set in init_display()

pos_y = 125;

layer = 15;

font fnt_century12;

string str_bps;

}

 

  There we go. I think that's about it. Now we can get to some programming.

  I think we will begin with the grace period code. When a player first joins while he is connecting and getting set up and oriented we don't want the player's that are already in the game killing him before he even has a chance to move. So what we are going to do is give the player a grace period inwhich he cannot be hit and he cannot fire his weapon either. What he can do is move around before he gets blasted and he can also pick up ammo and lunar candy during this period. So let's add that code first in function move_player.

 

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

 

my.transparent = ON; // create as transparent until grace period over

my.alpha = 20;

my.grace_time = PLAYER_GRACE_TIME; // set time left in grace period

send_skill(my.grace_time,0);

 

my.ammunition_remaining = AMMO_GAME_START; // rounds of ammunition at creation

 

  In this code we set the player's transparency on and set the alpha so he is barely visible to start with. What we will do shortly is cause him to become more visible the closer to the time his grace period is over. He will become completely visible when his grace period is over and he can be attacked. We also set the time left in his grace period and send it to the client(player) who created this entity.

  Now in function move_player add this code in the while(1) loop.

 

while(1)

{

 

// I am in grace period, amke sure everyone knows that and make me

// transparent until I can be hit

if(my.grace_time)

{

my.grace_time -= 1; // decrement grace time

 

// make player more visible

my.alpha = 20 + ((PLAYER_GRACE_TIME-my.grace_time)/PLAYER_GRACE_TIME)*50;

 

// Oops, grace period over

if(my.grace_time == 0)

{

my.transparent = OFF; // make player totally visible, grace is over

send_skill(my.grace_time, 0); // make sure client that created ent knows

// grace period is over

}

}

 

  We first check if grace period is active for this player, if(my.grace_time). If he is still in grace period we next decrement the time left in grace period, my.grace_time -= 1.

  Then we use this nifty little nightmare of an equation I made that basically takes the alpha of the player from 20 to 70 not matter what you set the PLAYER_GRACE_TIME to. Don't ask me how it works.

my.alpha = 20 + ((PLAYER_GRACE_TIME-my.grace_time)/PLAYER_GRACE_TIME)*50;

  Then we check if the grace_time has expired, if(my.grace_time == 0). If it has we turn off transparency and let the player who was in the grace period he is now free game.

my.transparent = OFF; // make player totally visible, grace is over

send_skill(my.grace_time, 0); // make sure client that created ent knows

// grace period is over

  Now in function main() add this code.

 

// display player specific text

txt_player_name.visible = ON;

txt_health.visible = ON;

txt_ammo_remaining.visible = ON;

 

// display loading message

str_cpy(str_status_message, "Initializing Player...");

txt_status_message.visible = ON;

 

// after player has a weapon we can continue to

// grace period message

while(player.weapon == 0)

{

wait(1);

}

 

str_cpy(str_status_message, "Grace Period Active");

 

 

 Here we just display a loading message until the player's weapon is set. After that we continue on to the grace period message. In the while(1) loop in main() add this script.

 

if (dplay_latency == 0 && dplay_bps == 0 && connection == 2)

{

exit;

}

 

//dedicated server skips this

if (connection != 1)

{

// if grace period over, hide message

if (player.grace_time == 0)

{

txt_status_message.visible = OFF;

}

}

 

  We check if the computer is a dedicated server, if (connection != 1). If the computer is Server then it doesn't need to worry about displaying grace period message. Then we check if garce period is over, if (player.grace_time == 0). If grace period is over we turn off the grace period message, txt_status_message.visible = OFF.

  Then in function init_display() add these two lines of code to set status message position to center of screen.

 

txt_number_of_players.pos_x = screen_size.x - 350;

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

txt_status_message.pos_y = screen_size.y/2;

 

 Let's do a quick test run on Host from SED. You should now see something like this when you select a profession.

  I actually think this program is starting to look like a game. Next, let's set up our input code to fire weapon. We are going to do this a bit differently then our movement code. I decided to make it so the player can't just hold down the fire button (<space> key) and continually fire. Instead I decided to use on_space = function command so the player has to let off of key and re-hit it. Also, I put a recharge time on weapon so he can't constantly hit the <space> key. Good players will figure out about the exact recharge time and time their <space> key hits so they can get the maximum number of rounds the fired in the shortest amount of time.

  So at bottom of the .wdl file add this line of code.

 

on_enter = chat_entry; // on <enter> start chat entry

on_space = fire_weapon; // on <space> shoot weapon

 

  The right above all the on_ commands add this function.

 

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

// Fire_Weapon - player hit key to fire weapon, so let server know

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

function fire_weapon()

{

// fire weapon if have ammo, not reloading and not in grace peiod

if ((player.ammunition_remaining > 0) && (weapon_recharge_count == WEAPON_RECHARGE_DELAY)

&& (player.grace_time == 0))

{

weapon_recharge_count = 0; // set reload timer to 0

player.ammunition_remaining -= 1; // one less round ammo

player.firing_weapon = TRUE; // player is firing weapon

if (connection == 2) // client?

{

send_skill(player.ammunition_remaining,0); // send ammo left to server

send_skill(player.firing_weapon,0); // let server know we are firing weapon

}

}

}

 

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

on_client = client_called; // on client event, call cleint_called()

 

  So when the player hits the <space> key, the fire_weapon() function is called. The program the checks to make sure that the player has ammunition, he is not in grace period and that his weapon is not reloading.

if ((player.ammunition_remaining > 0) && (weapon_recharge_count == WEAPON_RECHARGE_DELAY)

&& (player.grace_time == 0))

  If he can fire weapon, the first thing we do is reset his weapon_recharge_count to zero. It will count up until the weapon is considered reloaded when it equal WEAPON_RECHARGE_DELAY. During that period he cannot fire his weapon again. We will add the recharge code here soon.

weapon_recharge_count = 0; // set reload timer to 0

  Then we decrement the ammo he has available.

player.ammunition_remaining -= 1; // one less round ammo

  After that, set the skill to let the server know the player just fired his weapon, the server uses this information to start attack animation on all the clients and to actually create and move the projectile.

player.firing_weapon = TRUE; // player is firing weapon

  Then, if this player is a client, we send the information to the server.

 

if (connection == 2) // client?

{

send_skill(player.ammunition_remaining,0); // send ammo left to server

send_skill(player.firing_weapon,0); // let server know we are firing weapon

}

 

  Now, lets add the code to make the weapon reload. Add this function right below fire_weapon().

 

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

// Function Reload_Weapon(): reload weapon (time inbetween attacks)

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

function reload_weapon()

{

while(1)

{

weapon_recharge_count += 1;

if(weapon_recharge_count > WEAPON_RECHARGE_DELAY)

{

weapon_recharge_count = WEAPON_RECHARGE_DELAY;

}

wait(1);

}

}

 

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

on_client = client_called; // on client event, call cleint_called

 

  This is pretty self-explainatory. It contually recharges weapon if it not recharge until it reaches WEAPON_RECHARGE_DELAY.

  Let's add the call to this function from main() now.

 

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

init_chat(); // intitialize chat variables

reload_weapon(); // continually recharges weapon

 

  Next we need to add the code to the server to handle the firing_weapon skill we sent from the client to make everything happen. So in while(1) loop in function move_player add this script.

 

// if firing weapon set how long we want attack animation to run

if(my.firing_weapon == TRUE)

{

my.animation_attack_life = 20;

}

 

// if still running attack animation, attack animation overides others

if(my.animation_attack_life > 0)

{

my.animation_state = ANIMATION_STATE_ATTACK;

my.animation_attack_life -= 1;

}

 

// if animation state changed, send new animation state to all clients

// so they can do animation locally

if(my.animation_state != my.prev_anim_state)

{

send_skill(my.animation_state,SEND_ALL);

}

 

  When the server first receives the firing_weapon skill from the client, it immediately set's the number of frames to run the attack animation.

if(my.firing_weapon == TRUE)

{

my.animation_attack_life = 20;

}

  Then while the attack animation is still running, it supercedes all other animations and decrements by one each frame.

// if still running attack animation, attack animation overides others

if(my.animation_attack_life > 0)

{

my.animation_state = ANIMATION_STATE_ATTACK;

my.animation_attack_life -= 1;

}

  A bit lower in the same while(1) loop in move_player we need to add this code right below the scan floor script.

 

RESULT = TRACE(vecFrom,vecTo);

my.Z = TARGET.Z + ((my.MAX_Z - my.MIN_Z)/2);//Set to the floor

}

*/

 

// if ent just hit attack key, create projectile depending on weapon

if (my.firing_weapon == TRUE)

{

if(my.weapon == WEAPON_NUCLEAR_ROD)

{

ent_create(str_radiation_beam, my.X, projectile_function);

}

 

if (my.weapon == WEAPON_PHOTOSYNTHESIS_EMULATOR)

{

ent_create(str_petri_dish, my.X, projectile_function);

}

 

if (my.weapon == WEAPON_COMPRESSION_RIFLE)

{

ent_create(str_bullet, my.X, projectile_function);

}

 

my.firing_weapon = FALSE; // projectile sent, no longer firing

}

 

  This is the code that actually creates the projectile entity and starts it's function depending on the player's weapon. The it set's the firing_weapon skill to FALSE. The projectiles will be moved globally on the server, just like the players are.

  Let's go ahead and add the projectile_function() now right above function move_player.

 

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

// Function Projectile_Function: create correct projectile for weapon

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

function projectile_function()

{

 

my.enable_entity = ON;

my.enable_block = ON;

my.event = projectile_event;

 

my.z += 25; // fire above waist level

 

//my.y = you.y + 8 * sin(you.pan) - 8 * cos(you.pan); //offset projectile right

 

my.speed = PROJECTILE_SPEED; // get speed

my.pan = you.pan; // set pan to owners pan

my.force_y = 0;

my.force_z = 0;

my.owned_by_hdl = handle(you);

 

//if player carries nuclear rod 2d18 damage

if(you.weapon == WEAPON_NUCLEAR_ROD)

{

my.life_span = AMMO_LIFESPAN_NUCLEAR; // set range

my.damage_die_1 = 18; // set die 1 (damage dice)

my.damage_die_2 = 18; // set die 2

// play radition beam sound at player

ent_playsound (you, snd_radiation_beam, 40);

}

 

//if player carries photosynthesis emulator 2d13 damage

if(you.weapon == WEAPON_PHOTOSYNTHESIS_EMULATOR)

{

my.life_span = AMMO_LIFESPAN_EMULATOR; // set range

my.damage_die_1 = 13; // set die 1 (damage dice)

my.damage_die_2 = 13; // set die 2

// play petri dish sound at player

ent_playsound (you, snd_emulator, 45);

}

 

// if player carries rifle 2d8 damage

if(you.weapon == WEAPON_COMPRESSION_RIFLE)

{

my.life_span = AMMO_LIFESPAN_RIFLE; //set range

my.damage_die_1 = 8; // set die 1 (damage dice)

my.damage_die_2 = 8; // set die 2

// play bullet sound at player

ent_playsound (you, snd_rifle, 100);

}

 

// while projectile still alive, this is global

while (my.life_span != 0)

{

my.life_span -= 1;

// use time & speed

my.force_x = my.speed * time;

 

//A5 move

move_mode = ignore_passable + ignore_you + ignore_me;

ent_move(my.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, ignore_passable + ignore_you + ignore_me);

 

wait(1);

}

 

sleep(.1); // wait for sound to be played at projectile

// if projectile life_span over(at max range), remove projectile

ent_remove(my);

}

 

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

// MOVE - ATTACHED TO PLAYER IN CREATE_PLAYER() FUNCTION

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

 

  When we first create the projectile we enable_block and enable_entity and set it's event function to projectile_event(), which we will add after we are done with the explaination of this function. Enable_block handles the collisions with walls and static level geometry. Enable_entity handles the collisions with other entities.

my.enable_entity = ON;

my.enable_block = ON;

my.event = projectile_event;

  Next we raise the start position of the projectile up some so it is more at chest level of the player, my.z += 25. After that we set the speed and pan of the projectile. Since are bullets will be flying horizontally and straight we set force_y and force_z to zero.

  After that we save the handle to the owner of the bullet using the handle() command. We will later use this handle to get a pointer (using ptr_for_handle()) to the player that owns this entity from a local procedure on the clients. Since we got this handle on the server or host, this handle refers to the global player (the player on the server), if we would have gotten this handle from within the client's local procedure, the handle would refer to the entity (clone) on the client.

my.speed = PROJECTILE_SPEED; // get speed

my.pan = you.pan; // set pan to owners pan

my.force_y = 0;

my.force_z = 0;

my.owned_by_hdl = handle(you);

 

Note: If you are not familiar with handle() and ptr_for_handle() commands you may want to read the manual. Also I created a tutorial on this long ago called the Power of Handles, which if you search the 3DGS forums you should be able to find. Handles are a very useful tool in 3DGS so I would recommend learning how to use them. At some point you will need to use them if you are making a game almost guaranteed.

 

  I am not going to cover all the projectile types since they are the same exept for the damage values and sound they play, so let's just go through what happens if the weapon used was a nuclear rod. The reason we have to check what weapon was used was because all the projectiles when they are created call this one function, the projectile_function().

//if player carries nuclear rod 2d18 damage

if(you.weapon == WEAPON_NUCLEAR_ROD)

{

my.life_span = AMMO_LIFESPAN_NUCLEAR; // set range

my.damage_die_1 = 18; // set die 1 (damage dice)

my.damage_die_2 = 18; // set die 2

// play radition beam sound at player

ent_playsound (you, snd_radiation_beam, 40);

}

  First we check if the player that created this projectile is carrying a nuclear rod, if(you.weapon == WEAPON_NUCLEAR_ROD). If he is we then set the life_span of the projectile to AMMO_LIFESPAN_NUCLEAR, which is the range determined by frames. Then we set the damage of the two dice that will be rolled when the projectile hits another player and the roll for armor class save is made.

my.damage_die_1 = 18; // set die 1 (damage dice)

my.damage_die_2 = 18; // set die 2

  Then we play the sound for the radiation beam being fired, ent_playsound (you, snd_radiation_beam, 40).

  Let's skip the photosythesis emulator and compression rifle code since they are virtually the same.  

  Now we start the actual movement code with a while loop that continues until the projectile's life_span is over or until the projectile hits something and is removed, which will be handled by the projectile_event().

while (my.life_span != 0)

  The first thing we do within the while() loop is to decrement the projectile's life_span and then set it's force_x and move it. If it hits a wall or entity during move the projectile_event() will be called.

{

my.life_span -= 1;

// use time & speed

my.force_x = my.speed * time;

 

//A5 move

move_mode = ignore_passable + ignore_you + ignore_me;

ent_move(my.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, ignore_passable + ignore_you + ignore_me);

   And next we wait(1) and do it all again until projectile is removed or it's life_span expires.

wait(1);

}

  Then when the weapon's life_span reaches zero we exit the while loop and we wait 1/10th of a second to make sure entity still exist on the clients when we play the explosion sound and then we remove the projectile.

sleep(.1); // wait for sound to be played at projectile

// if projectile life_span over(at max range or hit something), remove projectile

ent_remove(my);

  Now let's add the projectile_event() function right above the projectile_function(). Are you feeling like a programmer? With all this code, you ought to. The projectile_event() is called when the projectile hits a entity or level geometry.

 

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

// Function Projectile_Event: projectile hit something

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

function projectile_event()

{

var random_num;

var dam; // total damage to entity hit

 

// projectile hit a entity in level

if(event_type == event_entity)

{

 

// if player hit is not still in grace period

// check for damage

if(you.grace_time == 0)

{

 

random_num = int(random(20))+1; // get d20 attack roll

// check if enemy's AC saved them from hit

if(random_num >= you.armor_class)

{

dam = two_dice_damage(); // do damage for this ammo type

you.health -= dam; // subtract damage from entity hit

ptrOwner = ptr_for_handle(my.owned_by_hdl);

ptrOwner.lunar_psychosis_rating += dam; // give 1 point per damage point

if(you.health <= 0) // if you killed enemy

{

ptrOwner.lunar_psychosis_rating += 500; // give 500 points for kill

}

// send needed skills to owner clients

send_skill(ptrOwner.lunar_psychosis_rating, 0);

 

// the below sent skills used for local effects on clients

vec_set(effect_pos.x, my.x); // set explosion position

send_var(effect_pos);

vec_set(effect_normal.x,normal); // set explosion normal

send_var(effect_normal);

 

do_effect_type = EFFECT_HIT_EXPLOSION; // set effect type

send_var(do_effect_type);

 

ent_playsound (my, snd_damage, 100); // play damage sound

 

if (connection != 1) // dedicated server need not do local effects

{

effect_local(effect_explosion,50,effect_pos.X,effect_normal.x);

}

}

else // richocet

{

// the below sent skills used for local effects on clients

vec_set(effect_pos.x, my.x); // set explosion position

send_var(effect_pos);

vec_set(effect_normal.x,normal); // set explosion normal

send_var(effect_normal);

 

do_effect_type = EFFECT_RICOCHET_EXPLOSION; // set effect type

send_var(do_effect_type);

 

ent_playsound (my, snd_no_damage, 100); // play ricochet sound

 

if (connection != 1) // dedicated server need not do local effects

{

effect_local(effect_explosion,20,effect_pos.X,effect_normal.x);

}

}

}

}

 

// projectile hit level geometry

if (event_type == event_block)

{

// the below sent skills used for local effects on clients

vec_set(effect_pos.x, my.x); // set explosion position

send_var(effect_pos);

vec_set(effect_normal.x,normal); // set explosion normal

send_var(effect_normal);

 

do_effect_type = EFFECT_RICOCHET_EXPLOSION; // set effect type

send_var(do_effect_type);

 

ent_playsound (my, snd_no_damage, 100); // play ricochet sound

 

if (connection != 1) // dedicated server need not do local effects

{

effect_local(effect_explosion,20,effect_pos.X,effect_normal.x);

}

 

}

 

my.life_span = 0; // set projectile to dead

}

 

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

// Function Projectile_Function: create correct projectile for weapon

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

 

  Time for more explaining I suppose. We check to see if the event that got us here was a collision with and entity.

if(event_type == event_entity)

  If it was, the we check to see if the entity we hit is in it's grace period.

if(you.grace_time == 0)

  If the entity hit wasn't in it's grace period then we go ahead and do a hit roll and see if the armor of the entity deflected the hit.

random_num = int(random(20))+1; // get d20 attack roll

// check if enemy's AC saved them from hit

if(random_num >= you.armor_class)

{

  If the armor didn't deflect the projectile then we do damage to the entity it hit.

dam = two_dice_damage(); // do damage for this ammo type

you.health -= dam; // subtract damage from entity hit

  Now we get the owner using ptr_for_handle() and the skill that we saved the handle of the owner of this projectile into. Then we add 1 point to score for every hitpoint of damage plus 500 points if we killed a player.

ptrOwner = ptr_for_handle(my.owned_by_hdl);

ptrOwner.lunar_psychosis_rating += dam; // give 1 point per damage point

if(you.health <= 0) // if you killed enemy

{

ptrOwner.lunar_psychosis_rating += 500; // give 500 points for kill

}

  We next send the new score to the player that owned the projectile that scored a hit.

send_skill(ptrOwner.lunar_psychosis_rating, 0);

  We don't need to send the new health value to the fool that got hit by the projectile because that is going to be handled in the function move_player, which is going to check if the player's health != health_old to determine whether to send health. It will be just about the same method we have used for forces earlier. The reason for this is that we also will be adding some rest and exhaustion code that will also be changing the player's health and we don't want to possibly be sending the health three times for the 3 events that can change the health (got hit, resting, or running(exhaustion)) at the same time.

  Then we send all the information need for the local_effect() function (will add that here soon) to do the did damage explosion locally on clients and go ahead and play the damage sound and do the local explosion for the host or single player, a dedicated server doesn't need to waste time doing a local effect it can't see anyway.

// the below sent skills used for local effects on clients

vec_set(effect_pos.x, my.x); // set explosion position

send_var(effect_pos);

vec_set(effect_normal.x,normal); // set explosion normal

send_var(effect_normal);

 

do_effect_type = EFFECT_HIT_EXPLOSION; // set effect type

send_var(do_effect_type);

 

ent_playsound (my, snd_damage, 100); // play damage sound

 

if (connection != 1) // dedicated server need not do local effects

{

effect_local(effect_explosion,50,effect_pos.X,effect_normal.x);

}

  If the projectile was deflected by armor we set and send the correct data for ricochet to the clients, play the ricchet sound and do local explosion on host or single player.

else // richocet

{

// the below sent skills used for local effects on clients

vec_set(effect_pos.x, my.x); // set explosion position

send_var(effect_pos);

vec_set(effect_normal.x,normal); // set explosion normal

send_var(effect_normal);

 

do_effect_type = EFFECT_RICOCHET_EXPLOSION; // set effect type

send_var(do_effect_type);

 

ent_playsound (my, snd_no_damage, 100); // play ricochet sound

 

if (connection != 1) // dedicated server need not do local effects

{

effect_local(effect_explosion,20,effect_pos.X,effect_normal.x);

}

}

 

  Now, if the projectile hit level geometry, we also send data, play sound, and do richochet explosion locally on host or single player.

// projectile hit level geometry

if (event_type == event_block)

{

// the below sent skills used for local effects on clients

vec_set(effect_pos.x, my.x); // set explosion position

send_var(effect_pos);

vec_set(effect_normal.x,normal); // set explosion normal

send_var(effect_normal);

 

do_effect_type = EFFECT_RICOCHET_EXPLOSION; // set effect type

send_var(do_effect_type);

 

ent_playsound (my, snd_no_damage, 100); // play ricochet sound

 

if (connection != 1) // dedicated server need not do local effects

{

effect_local(effect_explosion,20,effect_pos.X,effect_normal.x);

}

 

}

  And at the bottom of the projectile_event() function, which is only ever called when a projectile hits something, we set the projectile's life_span to zero after all actions have been completed for the hit and the projectile_function() will remove the projectile.

my.life_span = 0; // set projectile to dead

}

  Now we need to add the local_effect() function, which will run on the clients and do the local effects when it receives the information to do so. Go ahead and add it above the projectile_event() function.

 

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

// Function Local_Effect: continually checks if local effects need done

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

function local_effect()

{

while(connection == 2) // only client connection needs this function

{

if (do_effect_type == EFFECT_HIT_EXPLOSION)

{

effect_local(effect_explosion,50,effect_pos.X,effect_normal.x);

do_effect_type = 0;

}

if (do_effect_type == EFFECT_RICOCHET_EXPLOSION)

{

effect_local(effect_explosion,20,effect_pos.X,effect_normal.x);

do_effect_type = 0;

}

 

wait(1);

}

}

 

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

// Function Projectile_Event: projectile hit something

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

 

  We will call this function from main() and it will continually run. What happens is the host or server will first send the necessary data for the effect like the explosion normal and position, then it sends the type of effect to do and when the client gets the do_effect_type it uses the previously sent data to do the correct effect.

  Let's go ahead and add the call from main() before we forget.

 

// 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

init_chat(); // intitialize chat variables

reload_weapon(); // continually recharges weapon

local_effect(); // continually check for effect to be run on client

 

  So now let's add the functions for the explosion above local_effect(). These functions are directly from the old 3DGS templates and I am not going to explain what they do.

 

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

// Function Vec_Randomize(): randomize a vector for explosion

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

function vec_randomize(&vec,range)

{

vec[0] = random(1) - 0.5;

vec[1] = random(1) - 0.5;

vec[2] = random(1) - 0.5;

vec_normalize(vec, random(range));

}

 

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

// Function Part_Alphafade(): fade particle over time

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

function part_alphafade()

{

my.alpha -= time+time;

if (my.alpha <= 0) { my.lifespan = 0; }

}

 

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

// Function Effect_Explosion(): do an explosion

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

function effect_explosion()

{

vec_randomize(temp_loc,30);

vec_add(my.vel_x, temp_loc);

my.alpha = 15 + random(5);

my.bmap = scatter_map;

my.flare = on;

my.bright = on;

my.beam = on;

my.move = on;

my.function = part_alphafade;

}

 

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

// Function Local_Effect: continually checks if local effects need done

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

 

  We also added a function to roll the 2 dice that are stored in the skills, damage_die_1 and damage_die_2, let's add two_dice_damage() now above vec_randomize().

 

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

// Function Two_Dice_damage - rolls 2 dice stored in skills

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

function two_dice_damage()

{

var random1 = 0; // random numbers

var random2 = 0;

var dam; // total damage

 

// Also, my.damage_die1 should always be a positive number

if ((my.damage_die_1 <= 0)&&(my.damage_die_2 <= 0)||(my.damage_die_1 <=0))

// The below commented code is better, but would be harder to understand

// it does the exact same thing as above though

// if ((my.damage_die_1 <= 0)||(my.damage_die_2 < 0))

{

return(0);

}

 

random1 = int(random(my.damage_die_1)) + 1; // get roll die 1

 

// we will give the ability to have 1 die damage by setting my.damage_die_2

// to 0

if (my.damage_die_2 != 0)

{

random2 = int(random(my.damage_die_2)) + 1; // get roll die 2

}

 

dam = random1 + random2; // get damage

return (dam); // return damage

}

 

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

// Function Vec_Randomize(): randomize a vector for explosion

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

 

  This function just rolls 2 dice using the damage_die skills (1 to damage_die_1) + (1 to damage_die_2) and then returns the result.

  Are you ready for a test run to see if all that code actually works? Go ahead and Publish or Resource and the run as Host and Client. You should now be able to fire and do damage like this.

  If you play around with it a bit you will notice no one dies. This is a humane game where no one is killed. Not! We will add that code here soon. Let's finished displaying all the text first. In init_display() add this code.

 

txt_number_of_players.pos_x = screen_size.x - 350;

txt_latency.pos_x = screen_size.x - 350;

txt_bps.pos_x = screen_size.x - 350;

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

 

  Then in display_info() function add this.

 

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

 

str_cpy(str_latency, "Latency: ");

str_for_num(str_temp, dplay_latency);

str_cat(str_latency, str_temp);

 

str_cpy(str_bps, "BPS: ");

str_for_num(str_temp, dplay_bps);

str_cat(str_bps, str_temp);

 

  And this.

 

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_lunar_psychosis_rating, "Score (LPR): ");

str_for_num(str_temp, int(player.lunar_psychosis_rating));

str_cat(str_lunar_psychosis_rating, str_temp);

 

  Then in function main() add this.

 

// turn off all text not needed for profession selection

txt_health.visible = OFF;

txt_ammo_remaining.visible = OFF;

txt_player_number.visible = OFF;

txt_lunar_psychosis_rating.visible = OFF;

 

 And this.

 

// display player specific text

txt_health.visible = ON;

txt_ammo_remaining.visible = ON;

txt_player_number.visible = ON;

txt_lunar_psychosis_rating.visible = ON;

 

  Also, let's add this too.

 

// 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;

txt_bps.visible = ON;

txt_latency.visible = ON;

}

 

  Now that our text display is all set up, let's get on to the death code. What we have to consider here is that when a player dies we are not exiting him from game, instead we are going to send him back to the profession selection panels. So when he dies we need to set restart positions in the necessary functions to make this happen. Let's go ahead and start in function main(). Add this code.

 

// if host, count yourself as a connection

if (connection == 3)

{

people_connected = 1;

}

 

restart_main: // on death restart here

 

vec_set(camera.pos,start_cam_pos); // get original camera position

vec_set(camera.pan,start_cam_pan);

 

  The vec_set() commands are placing are camera back to it's original position. Let's go ahead and add the code to save the original camera position now too.

 

level_load(world_str); // load level

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

 

vec_set(start_cam_pos,camera.pos); // save camera's original poistion for death

vec_set(start_cam_pan,camera.pan);

 

  Now let's add the goto() statement that will get us back to restart_main label.

 

// main game loop

while(1)

{

// server disconnected so quit client too

if (dplay_latency == 0 && dplay_bps == 0 && connection == 2)

{

exit;

}

 

if((!player) && (connection!=1)) // if player is dead restart profession selection

{

profession_not_set = TRUE; // no profession until select again

goto(restart_main);

}

 

  I was just thinking (you didn't know I could think, did you?), that the game wouldn't exit if the server or host disconnects during profession selection, so in main() add this.

 

vec_set(start_cam_pos,camera.pos); // save camera's original poistion for death

vec_set(start_cam_pan,camera.pan);

 

check_server_disconnect(); // continually checks is server is gone

 

  Then add this function under main().

 

wait(1);

}

 

}

 

function check_server_disconnect()

{

while(1)

{

// server disconnected so quit client too

if (dplay_latency == 0 && dplay_bps == 0 && connection == 2)

{

exit;

}

wait(1);

}

}

 

  And remove this code from main().

 

// main game loop

while(1)

{

// server disconnected so quit client too

if (dplay_latency == 0 && dplay_bps == 0 && connection == 2)

{

exit;

}

 

  That kind of sucked didn't it. I made a mistake there, sorry about that.

  Now let's make it so the players can actually die. In function move_player change this code.

 

while(1)

{

 

// I am in grace period, amke sure everyone knows that and make me

// transparent until I can be hit

if(my.grace_time)

{

 

  To this.

 

while(my.health > 0) // while this entity is alive

{

 

// I am in grace period, amke sure everyone knows that and make me

// transparent until I can be hit

if(my.grace_time)

{

 

  And then beneath that while() statement add this code.

 

my.firing_weapon = FALSE; // projectile sent, no longer firing

}

 

wait(1);

}

 

number_of_players -= 1; // decrement number of players

player_number_quit = my.player_number; // save player number who was killed

// Go through all entities, any player# that was above mine, decrement his player number

you = ent_next(NULL);

while(you != NULL)

{

if(you.player_number > player_number_quit)

{

you.player_number -= 1;

send_skill(you.player_number, SEND_ALL); // send new player#

}

you =ent_next(you);

}

 

send_var(number_of_players); // let everyone know new number of players

ent_remove(my); // remove ent of player that died

 

  This code does exactly the same thing as out player_events() code did when a player got disconnected or quit.

  Now, let's go ahead and add the resting and exhaustion code into action move. First lets declare a variable to store what the player's integer health was last frame.

 

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

// MOVE_PLAYER

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

function move_player

{

var health_old;

 

  Now let's add the code to send the health to the owner of this entity if the integer health has changed and then save this frames health to compare with next frame in move_player.

 

while(my.health > 0) // while this entity is alive

{

 

// if integer health change since last frame send it to client

if(int(my.health) != health_old)

{

send_skill(my.health,0); //send changed health to client owner

}

 

health_old = int(my.health); // save old health for next frame

 

  Under that we add the resting and exhaustion code. This code just adds health if not moving and takes health off if running.

 

send_skill(my.health,0); //send changed health to client owner

}

 

health_old = int(my.health); // save old health for next frame

 

// Resting code

// if standing

if ((my.force_x == 0)&&(my.force_y == 0))

{

my.health += .005; // add some health

if(my.health > my.max_health)

{

my.health = my.max_health;

}

}

 

// Exhaustion code

// if running

if(abs(my.force_x) > 1)

{

my.health -= .02; // subtract some health

if(my.health < 1) // don't let player run himself to death

{

my.health = 1;

}

}

 

Ok, lets give it another test run. Run as server and client. When you player runs (<shift> key) now his health will go down over time as shown below and when he is just standing his health will gradually go up.

 

  Oh, what the heck, give it a spin on your primary computer in single player mode by leaving the command line blank.

  After that change the command line to, -sv, and start the game and then join a client from your secondary computer. When you run in dedicated server mode you will see a screen like this on your primary computer.

 

 

  Thank goodness, this chapter is now over. The game is actually pretty much complete, but we still have a few chapters left, but they will be easy ones.

 

Previous Page Contents Next Page