Code snippets

Top  Previous  Next

Intelligent elevators

I'm pretty sure that when you read the title of this article you said something like: "Yeah, right... how can you associate elevators with intelligence?" The elevator code in this article will start to work if the player is approaching a switch. The "intelligent" idea is that the switch and its corresponding elevator aren't connected at all! You can use many elevators and many switches and they will know how to work together without using pointers, handles, etc. You will use the same action for every button and for every platform and the code will do the rest! Now that I've got your attention, let's see how it is done.

The idea is (once again) simple: if you create a normal level, the distance between the elevators placed inside the level isn't that small. I am using a button that scans around it and if it finds an elevator entity nearby it starts to use it. Here's the code associated to the elevator button:

action elevator_button
{
    while (1)
    {
         while (vec_dist (player.x, my.x) > 50) {wait (1);}
         temp.pan = 360; // scan on a sphere
         temp.tilt = 360; // because the elevator can be anywhere around this button
         temp.z = 500; // the elevator should be closer than 500 quants all the time
         scan_entity (my.x, temp);
         sleep (0.4); // scan 2 times a second (sleep (0.4 + 0.1))
         my.ambient = 100;
         sleep (0.1);
         my.ambient = 0;
    }
}

If the player approaches the button, the button will start to scan around it on a 500 quants radius. The button will flash because we are changing its ambient.

The action associated to the elevator entity is:

action elevator_entity
{
    if (my.elevator_speed == 0) // if we forget to set skill1 in Wed
    {
         my.elevator_speed = 5; // set the default speed value
    }
    if (my.elevator_height == 0) // if we forget to set skill2 in Wed
    {
         beep; beep; exit; // shut down the engine
    }
    my.enable_scan = on;
    my.event = move_elevator;
}

The elevator has a default speed value; elevator_speed = 5 if we forget to fill in skill1 in Wed. Skill2 is the destination point height (elevator_height) and should be filled every time. I wanted to make sure that you don't forget to do that so the engine will beep two times and then it will shut down if you forget about skill2. If the destination point really needs to be zero, set skill2 to 0.001 or so.

The elevator is sensitive to scanning; when this happens (the player comes close to a button placed close to the elevator), the event will be triggered:

function move_elevator()
{
    proc_kill(1);
    my.enable_scan = off;

We kill all the other instance of this function then we make the elevator insensitive to other scans.

    if (my.z < you.z)
    {
         while (my.z < (player.z - (player.max_z - player.min_z) / 2))
         {
              my.z += my.elevator_speed * time;
              wait (1);
         }
    }

If the elevator is placed behind the button, it must go up until it reaches the height that corresponds to player's feet.

    if (my.z > you.z)
    {
         while (my.z > (player.z - (player.max_z - player.min_z) / 2))
         {
              my.z -= my.elevator_speed * time;
              wait (1);
         }
    }

Same thing here; the elevator goes down until its height is below player's feet.

    my.z = player.z - (player.max_z - player.min_z) / 2;

We set the height of the elevator at player's feet (it was a little smaller or bigger).

 
    while (vec_dist (player.x, my.x) > 50) {wait (1);}
    my.enable_scan = on;
    vec_set (player_strength, strength); // store player's speed on x, y, z
    vec_set (strength, nullvector); // stop the player

We wait until the player has come close enough to the center of the platform and then we enable scanning again. We store player's speed (strength) and then we reset strength because we want to stop the player for now (we allow him to rotate).

    while (my.z < my.elevator_height)
    {
         my.z += my.elevator_speed * time;
         player.x = my.x;
         player.y = my.y;
         player.z = my.z + (player.max_z - player.min_z) / 2;
         wait (1);
    }

As long as elevator's z coordinate is below the value set in skill2, its z is increased and the player is kept in the center (and on top) of the platform by changing its x y z coords.

    while (my.z > my.elevator_height)
    {
         my.z -= my.elevator_speed * time;
         player.x = my.x;
         player.y = my.y;
         player.z = my.z + (player.max_z - player.min_z) / 2;
         wait (1);
    }

Same thing here, if the elevator needs to go down.

    vec_set (strength, player_strength);
}

When the elevator his reached its destination, we allow the player to move again by restoring strength.

I have provided an example level with two elevators and two buttons. Of course that you can add as many elevators and buttons as you want, as long as they aren't too close to each other. Play with temp.z = 500 in elevator_button to set different scanning ranges; the distance between my elevators is over 500 quants.



Inventory

This standalone project teaches you how to create an inventory. The player will be able to pick up a mace, a shield, body armor and the ring of fire. He can let them stay in the inventory or he can use them in order to improve its abilities. Let's take a look at the main function:

function main()
{
    level_load (inventory_wmb);
    wait (2);
    clip_size = 0;
    on_d = null; // disable the debug panel (key "D") because it is used for movement
    fps_max = 40;
    check_inventory();
}

I have disabled the "D" key because I'm using WSAD to move the player. Before I throw in the frightening check_inventory function, let's see some defines:

define items skill30;
define armour skill41;
define attack skill42;
define strength skill43;

define shield 1;
define mace 2;
define armor 4;
define ring 8;

When the player picks up an item, its corresponding value is added to player.items (just another name for skill30); if the player collects a shield and a ring, player.items will be 1 (shield) + 8 (ring) = 9, got it? Let's see the function:

function check_inventory()
{
    while (1)
    {
         if (player.items > 0)
         {

If the player has got at least an item (player.items > 0)

              if (player.items == 1) // 1 + 0 + 0 + 0
              {
                   shield_pan.visible = on;
                   mace_pan.visible = off;
                   armor_pan.visible = off;
                   ring_pan.visible = off;
              }

If player.items = 1, the player has got the shield; only shield_pan is made visible.

              if (player.items == 2) // 0 + 2 + 0 + 0
              {
                   shield_pan.visible = off;
                   mace_pan.visible = on;
                   armor_pan.visible = off;
                   ring_pan.visible = off;
              }

If player.items = 2, the player has got the mace; only mace_pan is made visible.

              if (player.items == 3) // 1 + 2 + 0 + 0
              {
                   shield_pan.visible = on;
                   mace_pan.visible = on;
                   armor_pan.visible = off;
                   ring_pan.visible = off;
              }

If player.items = 3, the player has got the shield and the mace; shield_pan and mace_pan are made visible.

               ...................................

The things repeat for player.items = 1...15

              if (player.items == 15) // 1 + 2 + 4 + 8
              {
                   shield_pan.visible = on;
                   mace_pan.visible = on;
                   armor_pan.visible = on;
                   ring_pan.visible = on;
              }
         }

If player.items = 15, the player has got all the items so all the panels are made visible.

         else
         {
              shield_pan.visible = off;
              mace_pan.visible = off;
              armor_pan.visible = off;
              ring_pan.visible = off;
         }
         mouse_map = cursor_pcx;
         mouse_pos.x = pointer.x;
         mouse_pos.y = pointer.y;

If player.items = 0, all the panels are invisible; we set the mouse pointer to be cursor_pcx; the position of the mouse will be calculated every frame.

         if (mouse_right == 1)
         {
              mouse_mode = (mouse_mode + 2) % 4;
              while (mouse_right == 1) {wait (1);}
         }
         wait (1);
    }
}

If we press the right mouse button, mouse mode will be set to 0 or 2, depending on how many times we press it. This shows / hides the mouse pointer, allowing us to click the items in the inventory in order to use them. The while loop waits until we release the right mouse button otherwise the pointer would appear / disappear every frame.

I use 5 panels: the main inventory panel and another 4 panels for the items.

panel main_pan // the main panel for the items in the inventory
{
    bmap = main_pcx;
    pos_x = 700;
    pos_y = 0;
    layer = 10;
    hbar = 7, 488, 85, blue_pcx, 1, player.armour;
    hbar = 7, 518, 85, red_pcx, 1, player.attack;
    hbar = 7, 550, 85, green_pcx, 1, player.strength;
    flags = refresh, d3d, visible;
}

The main panel includes 3 hbars that show player's armor (I have used armour because armor is used), attack and strength.

panel shield_pan // displays the shield on the panel
{
    bmap = shield_pcx;
    pos_x = 715;
    pos_y = 30;
    layer = 11;
    flags = overlay, refresh, d3d;
    on_click use_shield;
}

panel mace_pan // displays the mace on the panel
{
    bmap = mace_pcx;
    pos_x = 730;
    pos_y = 145;
    layer = 11;
    flags = overlay, refresh, d3d;
    on_click use_mace;
}

panel armor_pan // displays the armor on the panel
{
    bmap = armor_pcx;
    pos_x = 710;
    pos_y = 255;
    layer = 11;
    flags = overlay, refresh, d3d;
    on_click use_armor;
}

panel ring_pan // displays the ring on the panel
{
    bmap = ring_pcx;
    pos_x = 710;
    pos_y = 365;
    layer = 11;
    flags = overlay, refresh, d3d;
    on_click use_ring;
}

The panels used for the items will appear over the main panel if they are picked up; before I forget, I have created the bitmaps for these panels by placing them inside a black room and taking screenshots. Every item panel has a simple function that will be executed if we click the panel:

function use_shield()
{
    player.items -= shield; // remove the shield from the panel
    ent_create(shield_mdl, player.pos, attach_item);
    player.strength += 30; // bigger hbar
}

We substract shield (1) from player.items; function check_inventory will remove its associated panel from the inventory. We create the "real" shield and we give it to the player, and then we increase strength; the corresponding hbar on the main panel will grow bigger.

function use_mace()
{
    player.items -= mace; // remove the mace from the panel
    ent_create(mace_mdl, player.pos, attach_item);
    player.attack += 50; // bigger hbar
}

We substract mace (2) from player.items; function check_inventory will take care of the rest. We create the mace and give it to the player; the attack hbar will grow.

function use_armor()
{
    player.items -= armor; // remove the armor from the panel
    ent_create(armor_mdl, player.pos, attach_item);
    player.armour += 40; // bigger hbar
}

We substract armor (4) from player.items; function check_inventory removes the panel from the inventory. We create the armor and give it to the player; the armour hbar will grow.

function attach_item()
{
    proc_late();
    my.passable = on;
    my.metal = on;
    my.albedo = 0;
    while(player != null)
    {
        vec_set(my.x, player.x);
        vec_set(my.pan, player.pan);
        my.frame = player.frame;
        my.next_frame = player.next_frame;
        wait(1);
    }
}

These 3 items are glued to the player because of this function:

function attach_item()
{
    proc_late();
    my.passable = on;
    my.metal = on;
    my.albedo = 0;
    while(player != null)
    {
         vec_set(my.x, player.x);
         vec_set(my.pan, player.pan);
         my.frame = player.frame;
         my.next_frame = player.next_frame;
         wait(1);
    }
}

The mace, the shield and the armor were animated together with the player and saved as separate models, so they use the same animation frames all the time. I have created the armor model by deleting all the triangles that weren't needed; I have scaled it up a little to make sure that it looks good on the player.

As long as the player exists, these items are placed at player's position, using its angles and frames all the time.

The ring is a special inventory item - let's see the code:

function use_ring()
{
    player.items -= ring;
    while (1)
    {
         if (mouse_left == 1 && mouse_mode == 0 && number_of_flames == 0) // the mouse pointer is invisible and not too many entities are visible
         {
              temp_angle = 0;
              while (temp_angle < 360)
              {
                   temp_angle += 3;
                   temp.x = player.x + 100 * cos(temp_angle);
                   temp.y = player.y + 100 * sin(temp_angle);
                   temp.z = player.z;
                   ent_create (fire_pcx, temp, fire_action);
              }
         }
         while (mouse_left == 1) {wait (1);}
         wait(1);
    }
}

We substract ring (8) from player.items and then we enter inside a while (1) loop. If we press the left mouse button and the mouse pointer is invisible and the number of flames is zero, we create 120 flames around the player, 100 quants away from the player and then we wait until we release the left mouse button.

function fire_action()
{
    number_of_flames += 1; // add another flame entity to this var
    my.passable = on;
    my.flare = on;
    my.bright = on;
    my.oriented = on;
    my.pan = random(360);
    while (my.pan < 1500)
    {
         my.pan += (5 + random(3)) * time;
         my.z -= 0.4 * time;
         wait (1);
    }
    number_of_flames -= 1;
    ent_remove (me);
}

We don't want to use autofire on the ring of fire so number_of_flames is increased with every flame that is created; this way we make sure that a single ring of fire can be generated and we keep a decent frame rate. The flames are passable and oriented by a random pan angle; they will rotate until their pan will be 1500 (it works :) and they will decrease their z at the same time. When all the flames have been removed, number_fo_flames is zero and function use_ring can create another ring of fire.

Time to take a look at the actions associated to the items that can be picked up:

action shield_init
{
    my.passable = on;
    while (player == null) {wait (1);}
    while (vec_dist (my.x, player.x) > 50)
    {
         ent_cycle("idle", my.skill20); // play stand frames animation
         my.skill20 += 3 * time; // "idle" animation speed
         my.skill20 %= 100;
         wait (1);
    }
    player.items += shield;
    ent_remove (me);
}

The shield is passable and plays its "idle" animation until the player comes closer than 50 quants to pick it up. When this happens, player.items is increased with shield (1) and the shield is removed. All the other actions are similar so I'll let you look at them:

action mace_init
{
    my.passable = on;
    while (player == null) {wait (1);}
    while (vec_dist (my.x, player.x) > 50)
    {
         ent_cycle("idle", my.skill20); // play stand frames animation
         my.skill20 += 3 * time; // "idle" animation speed
         my.skill20 %= 100;
         wait (1);
    }
    player.items += mace;
    ent_remove (me);
}

action armor_init
{
    my.passable = on;
    while (player == null) {wait (1);}
    while (vec_dist (my.x, player.x) > 50)
    {
         ent_cycle("stand", my.skill20); // play stand frames animation
         my.skill20 += 2 * time; // "stand" animation speed
         my.skill20 %= 100;
         wait (1);
    }
    player.items += armor;
    ent_remove (me);
}

action ring_init
{
    my.passable = on;
    while (player == null) {wait (1);}
    while (vec_dist (my.x, player.x) > 50)
    {
         ent_cycle("frame", my.skill20); // play "frame" animation
         my.skill20 += 2 * time; // "frame" animation speed
         my.skill20 %= 100;
         wait (1);
    }
    player.items += ring;
    ent_remove (me);
}

Ready for the final action? We are talking about player_moves, the action associated to the player:

action player_moves // attached to the player
{
    player = me; // I'm the player
    player.items = 0;
    player.armour = 20;
    player.attack = 30;
    player.strength = 35;

The player starts the game without any item and some values for armour, attack and strength.

    while (1)
    {
         camera.x = player.x - 200 * cos(player.pan); 
         camera.y = player.y - 200 * sin(player.pan); 
         camera.z = player.z + 200;
         camera.pan = player.pan; 
         camera.tilt = -30; 

The camera is placed 200 quants behind the player and above the player, looking down at it.

         my.pan += 4 * (key_a - key_d) * time - 20 * mouse_force.x * time; 
         player_speed.x = 10 * (key_w - key_s) * time; 
         player_speed.y = 0;
         player_speed.z = 0;

The player can rotate using the keys "A" and "D" or the mouse; it can move forward / back with the keys "W" and "S"

         if ((key_w + key_s) != 0) 
         {
              ent_cycle("walk", my.skill20); 
              my.skill20 += 4 * (key_w - key_s) * time; 
              my.skill20 %= 100;
         }

If the player is walking, the model will play its "walk" animation; the animation will be reversed if the player moves backwards.

         else
         {
              if (mouse_left != 1) 
              {
                   ent_cycle("stand", my.skill20); // play stand frames animation
                   my.skill20 += 2 * time; // "stand" animation speed
                   my.skill20 %= 100;
              }

If the left mouse button (LMB) isn't pressed, the player is standing so the model will play its "stand" animation.

              else // the player has pressed the left mouse button
              {
                   if (ring_pan.visible != on && mouse_mode == 0 && number_of_flames == 0) // the ring has been used
                   {
                        my.skill20 = 0;
                        ent_cycle("attack", my.skill20); // play stand frames animation
                        if (my.skill20 < 99)
                        {
                             my.skill20 += 0.5 * time; // "attack" animation played once (no loop)
                        }
                   }
                   while (mouse_left == 1) {wait (1);}
              }
         }

If LMB is pressed and the ring of fire was used and the mouse pointer is invisible and number_of_flames = 0, the model will play its "attack" animation frames once and then it will freeze at the last frame until we release the LMB.

         move_mode = ignore_passable;
         ent_move(player_speed, nullvector);
         wait (1);
    }
}

Finally, move_mode = ignore_passable allows the player to carry the items without being slowed down by them and the ent_move instruction produces the movement.

Ok, but I would like to kill my enemies using the ring of fire! How can I do that?
The easiest method would be (imo) to make the player scan 100 quants around its current position as long as number_of_flames != 0. Use enemies' events to decrease their health when event_type = event_scan. This way you don't need to perform many time - consuming ent_scan instructions for every flame.