Planet Survivors

This month the player will get to move using a slightly modified action that includes inertia and friction, enjoys some health packs and ammo packs, and then meets the first enemy on the planet: a giant ant that fires deadly projectiles. I won't discuss the changes to the movement code because they are really simple and I have used similar snippets in some of my previous magazines.

action health_pack
{
my.passable = on;
my.metal = on;
while (player_1st == null) {wait (1);}
while (vec_dist(player_1st.x, my.x) > 70)
{
my.pan += 3 * time;
wait (1);
}
snd_play (pickedup_wav, 50, 0);
ent_remove (my);
players_shield += 20;
players_shield = min(200, players_shield);
}

action ammo_pack
{
my.passable = on;
my.metal = on;
while (player_1st == null) {wait (1);}
while (vec_dist(player_1st.x, my.x) > 70)
{
my.pan += 3 * time;
wait (1);
}
snd_play (pickedup_wav, 50, 0);
ent_remove (my);
players_ammo += 50;
players_ammo = min(500, players_ammo);
}

The actions for the health packs and ammo packs are similar; we will discuss code the latter action. The ammo is passable and has its "metal" flag set. The ammo box will wait until the player_1st (the first person player) is created, and then it will wait until the player comes closer than 70 quants to the box, changing its pan angle (rotating) inside a loop until the player is close. As soon as the player approaches the box, a pickedup_wav sound is played, the box disappears and players_ammo increases by 50 bullets. The last line of code limits the ammo to 500 bullets. On a side note, players_shield is limited to 200 units.

Let's examine the code that drives the huge ant:

action ant1
{
var ant_speed;
var ant_gun1;
var ant_gun2;
my.skill20 = random(100); // choose a random "stand" frame for each ant
my.lightred = 0; // my.red for A6, etc
my.lightgreen = 255;
my.lightblue = 0;
my.light = on;
my.lightrange = 0;
my.skill48 = 100; // the ant has 100 health points
my.enable_impact = on;
my.enable_entity = on;
my.event = damage_ant;
while (player_1st == null) {wait (1);}

The ant chooses a random my.skill20 value from the very beginning; skill20 controls the animation frames, so if we place 10 ants in our level, their "stand" animations won't be in sync, which is a good thing for us. Each ant glows green and has 100 health points, which are stored in their own skill48. The ants are sensitive to impact with other entities and run their damage_ant event function every time they collide with another entity. The last line of code will instruct the ants to wait until the player is created.

while (my.skill48 > 0) // as long as I'm alive
{
if ((vec_dist (my.x, player_1st.x) < 5000) && (players_shield > 0))
{
vec_set(temp, player_1st.x);
vec_sub(temp, my.x);
vec_to_angle(my.pan, temp); // the ant rotates toward the player
temp.tilt = 0;
temp.roll = 0;
temp.pan = -my.pan;
vec_rotate(normal, temp);
temp.tilt = -asin(normal.x);
temp.roll = -asin(normal.y);
my.tilt += 0.1 * ang(temp.tilt - my.tilt); // play with 0.1
my.roll += 0.1 * ang(temp.roll - my.roll); // play with 0.1

As long as the ant is alive, it checks if the player has come closer than 5000 quants to it and if the player is alive. If both statements are true, the ant rotates toward the player, and then we rotate the normal in a direction that is the opposite of ant's pan angle. This allows us to compute the slopes for the tilt and roll angle; we use those values to keep a correct orientation for the ant on the terrain. You will note that tilt and roll are increased or decreased slowly; decrease "0.1" to get a smoother angle adjustment effect if you want to.

ant_speed.x = 25 * time;
ant_speed.y = 0;
vec_set (temp, my.x);
temp.z -= 1000;
trace_mode = ignore_me + use_box;
ant_speed.z = -trace (my.x, temp);
ent_move(ant_speed, nullvector);
ent_cycle("walk", my.skill19); // play "walk"
my.skill19 += 8 * time; // "walk" animation speed
my.skill19 %= 100; // loop the animation

The ant will move towards the player with the speed given by 25 * time and it will adjust its height depending on the height of the terrain below its feet. The "walk" animation speed is given by 8 * time.

if (vec_dist (my.x, player_1st.x) < 2000) // if the player is closer than 2000 quants attack him
{
my.skill20 = 0; // reset the "attack" frames
while (my.skill20 < 100)
{
ent_cycle("attack", my.skill20); // play "attack"
my.skill20 += 10 * time; // "attack" animation speed
if ((my.skill20 > 40) && ((total_frames % 10) == 1))
{
vec_for_vertex (ant_gun1, my, 273);
ent_create (antsbullet_mdl, ant_gun1, ant_hits_player);
}
if ((my.skill20 > 40) && ((total_frames % 10) == 5))
{
vec_for_vertex (ant_gun2, my, 282);
ent_create (antsbullet_mdl, ant_gun2, ant_hits_player);
}
wait (1);
}
}
}

If the player comes closer than 200 quants to the ant we reset the attacking frames (skill20) and then we play the attack animation by increasing skill20. If the ant has played more than 40% of its "attack" animation frames, it fires 6 bullets a second from each one of its guns, alternatively (ant_gun1 fires after 1, 11, 21, 31, ... frames and ant_gun2 fires after 5, 15, 25, 35, .... frames).

else // the player is farther than 5000 quants away
{
ent_cycle("stand", my.skill20); // play "stand"
my.skill20 += 2 * time; // "stand" animation speed
my.skill21 %= 100;
}
wait (1);
}
my.skill20 = 0;
while (my.skill20 < 90) // the enemy is dead
{
ent_cycle("death", my.skill20); // play "death"
my.skill20 += 7 * time; // "death" animation speed
wait (1);
}
my.alpha = 100;
my.transparent = on;
while (my.alpha > 0)
{
my.alpha -= 2 * time;
wait (1);
}
ent_remove (my);
}

If the player got farther than 5000 quants away, the ant will switch to "stand", playing this animation in a loop. If we get to the line that resets skill20 again, the ant is dead, so we play its "death" animation and then we fade away its transparent body. The last line removes the ant model from our level.

function damage_ant()
{
if (you.skill47 == 1) // if this is a bullet fired by player's gun
{
my.skill48 -= 10; // lose 10 health points
ent_playsound (my, antangry_wav, 3000);
}
}

The function above will run if one of player's bullets hits the ant. The first line checks if the ant has collided with a bullet or not (it could also collide with another ant, player's model, and so on) and if this is true, it subtracts 10 health points and plays an antangry_wav sound.

function ant_hits_player()
{
my.skill47 = 5; // that's an bullet fired by the ant
my.pan = you.pan; // the bullet and the ant have the same pan
my.tilt = you.tilt; // and tilt
my.enable_entity = on; // the bullet is sensitive to other entities
my.enable_impact = on;
my.enable_block = on; // and to level blocks
my.event = remove_bullet;
my.skill30 = 0;
while (my != null)
{
my.skill1 = 100 * time; // bullet speed
my.skill2 = 0;
if (my.skill30 < 1500)
{
my.skill3 = 0; // keep the bullet running in a straight line at the beginning
}
else
{
my.skill3 -= 1 * time; // make it descend after a while
}
move_mode = ignore_you + ignore_passable + ignore_push; // ignores the ant (its creator = you)
my.skill30 += ent_move(my.skill1, nullvector); // store the distance covered by ant's bullet in skill30
wait (1);
}
}

This function moves ant's bullet towards the player. I decided to allow the ants to miss most of the time because they have quite an impressive fire rate; this is why the bullets have the same tilt angle with ant's tilt angle. The bullets are sensitive to impact with other entities and level blocks / terrain and their associated event function is named remove_bullet. We reset skill30, and then we enter inside the while loop that keeps running for as long as the bullet exists. The bullet moves with the speed given by 100 * time; if it has covered less than 1500 quants, it keeps moving in a straight line, otherwise it will get a small negative speed on the z axis, which will make it impact with the ground. The bullet ignores its creator (the ant) and stores the result of its movement in its own skill30, used as a distance tracker inside the same loop.

function remove_antsbullet()
{
my.event = null;
ent_playsound (my, anthit_wav, 1000);
sleep (0.4);
my.passable = on;
my.alpha = 100;
my.transparent = on;
while (my.alpha > 0)
{
my.alpha -= 2 * time;
wait (1);
}
ent_remove (my);
}

The function above is the event for ant's bullets. It stops the bullet from reacting to other events, plays a anthit_wav sound, wait for a bit, and then makes the bullet passable and transparent, decreases its alpha in a loop and removes the bullet.

function hurt_player1st()
{
if (you.skill47 == 5)
{
my.event = null;
snd_play(playerhurt_wav, 50, 0);
players_shield -= 5;
sleep (0.5);
my.event = hurt_player1st;
}
players_shield = max(0, players_shield); // don't allow players_shield to go below 0
while (players_shield <= 0)
{
if (camera.roll < 70)
{
camera.roll += 5 * time;
}
wait (1);
}
}

The last function hurts the player if one of the bullets fired by the ant hits him or her. First of all, we check to see if the entity that has impacted with the player is an enemy bullet or not (skill47 = 5). If this is true we play a playerhurt_wav sound, we decrease players_shield, we make the player insensitive to other bullets for 0.5 seconds and then we set the event function again. We aren't allowing players_shield to go below zero (health = -15 wouldn't look to nice on a panel, right?) and then we change the roll angle of the camera inside a loop, in order to make the player a bit uncomfortable and let him know that he's dead now.

Flame thrower

If you need a weapon that creates a jet of particles then you have come to the right place.

action flame_thrower
{
my.metal = on;
while (player == null) {wait (1);} // wait until the player is loaded
while (vec_dist (my.x, player.x) > 60) // wait until the player has come close enough
{
my.pan += 5 * time;
wait (1);
}
snd_play(gotflames_wav, 60, 0);
my.passable = on;
while (1)
{
my.x = player.x + 20 * cos(player.pan) + 13 * sin(player.pan);
my.y = player.y + 20 * sin(player.pan) - 13 * cos(player.pan);
my.z = player.z + 12;
my.pan = camera.pan;
my.tilt = camera.tilt;
vec_for_vertex (flame_start, my, 14); // the flames will start at the coordinates given by the 14th vertex on the gun
vec_set(flame_end, nullvector); // reset flame_end
flame_end.x = flame_range.x; // add 400 quants to it
vec_rotate(flame_end, camera.pan); // rotate it depending on player's angles
vec_add(flame_end.x, camera.x); // now set the correct flame_end position in 1st person mode (use player.x instead of camera.x for 3rd person)
if(key_ctrl == on) // Ctrl was pressed?
{
create_flames(); // fire!
}
wait (1);
}
}

The weapon will wait until the player has come closer than 60 quants to it. As soon as this happens, the weapon becomes passable and is placed 20 quants in front of the player, 13 quants to the right and 12 quants above player's origin, regardless of player's position and / or angles. Play with 20, 13 and 12 to set a different position for the flame thrower in 1st person mode. The gun will have the same pan and tilt angles with the camera.

I have defined a variable named "flame_start" and I use vec_for_vertex to set it to the position of the 14th vertex on the flame thrower model every frame; this will be the origin for our flames. We reset the second variable named "flame_end", we add the content of "flame_range" to it (a variable that sets the range to 400 quants), and then we rotate "flame_end", creating a vector that has a magnitude of 400 quants and the orientation given by player's angles. Finally, we add camera's position to the vector; this way we have set the correct flame_end point, which is (surprisingly) the point where the flames will end.

Every time we press the "Ctrl" key, function create_flames() is run:

function create_flames()
{
vec_sub(flame_end.x, flame_start.x);
temp = vec_length(flame_end);
flame_end.x = (flame_end.x * 3) / temp;
flame_end.y = (flame_end.y * 3) / temp;
flame_end.z = (flame_end.z * 3) / temp;
while(temp > 0)
{
effect(particle_flames, 1, flame_start.x, nullvector);
vec_add(flame_start.x, flame_end.x);
temp -= 3;
}
}

This function gets the distance between flame_end and flame_start, and then splits it in tiny parts, creating particles until flame_start grows to flame_end. If you would be able to get a side view to our particle effect, which nowadays, using modern technology, is possible :) you would see something like this:

I am using many more particles for the effect; the shot above was taken with "3" being replaced by "20" in function "create_flames", so the effect in the picture uses about 7 times fewer particles when compared to the original flame thrower effect.

function particle_flames()
{
temp.x = random(2) - 1;
temp.y = random(2) - 1;
temp.z = random(2) - 1;
vec_set(my.vel_x, temp);
my.move = on;
my.bmap = fire_tga;
my.flare = on;
my.bright = on;
my.alpha = 100;
my.lifespan = 100;
my.size = 15;
my.function = fade_flames;
}

function fade_flames()
{
my.alpha -= 75 * time;
my.size += 10 * time;
if(my.alpha < 0) {my.lifespan = 0;}
}

The rest of the code is your standard particle effect; if you think that the fire effect lags a bit you can shorten the life of the particles. Oh, and if you set flame_start to your enemy's weapon and flame_end to be player's model you will see how it feels to be on the other side of the flame thrower. For those of you that are too lazy to try my snippets I've got a low quality movie file here. You will need Media Player 9 or a newer version of it.