Code snippets

Top  Previous  Next

Morrowing

 

aum50_shot_3

 

The last Morrowing episode (sniff) introduces a new character that speaks with the player and takes different actions depending on player's answers: it can fly away (really!) or it can fight the player. I won't concentrate my efforts on the code that deals with the dialogue; you can always examine its inner works in the Morrowing article from Aum49.

 

action enemy2 // attached to the berzerka.mdl model
{
       var enemy_speed;
       var trace_temp;
       var right_fist; // will store the coordinates of a vertex on the right fist
       var left_fist; // will store the coordinates of a vertex on the left fist
       var lines_on = 0; // makes sure that the lines aren't repeated several times
       my.health = 200; // this enemy has got 200 health points
       my.passable = on; // and is passable
       my.transparent = on; // and invisible at first
       my.alpha = 0;
       while (mace == null) {wait (1);} // wait until the player picks up and uses the mace
       my.passable = off;
       while (my.alpha < 95) // make the enemy fade in nicely
       {
               my.alpha += 5 * time;
               wait (1);
       }
       snd_play (beast2_wav, 100, 0); // play a frightening sound
       my.alpha = 100; // the enemy is supposed to be opaque here
       my.transparent = off;

 

The first few lines declare some local variables; the most important are right_fist and left_fist, which will be used for hand combat. The monster has 200 health points and it is invisible and passable at first; it will wait until the player picks up and uses the mace (mace != null), and then it will become impassable and opaque, increasing its alpha inside the while loop. When the monster is completely opaque it plays the beast2_wav sound.

 

       while (my.health > 0) // as long as the enemy is alive
       {
               if ((vec_dist (my.x, player.x) < 2000) && (player.health > 0)) // if the player is alive and comes closer than 2000 quants to the enemy
               {
                       lines_on += 1; // increment lines_on
                       if (lines_on == 1) // don's repeat these lines (say them only once)
                       {
                               // text processing starts here
                               dialog_number = 7; // player's line will be processed by the "if (dialog_number == 7)" branch inside the panelstexts.wdl file
                               str_cpy(temp_str, line16_str); // "Hey! What are you doing in my tent?"
                               process_their_strings();
                               wait (1);
                               theysay_txt.string = their_lines_str;
                               theysay_txt.visible = on;
                               wait (3);
                               stop_player = 1; // stop the player for now
                               sleep (2); // wait for 2 seconds
                               isay1_txt.string = line17_str; // "Me? It must be some sort of a misunderstanding, sir!"
                               isay2_txt.string = line18_str; // "Stop bugging me! I'm only trying to get some equipment!"
                               isay1_txt.visible = on; // give the player the possibility to answer (this will bring up the my_pan panel as well)
                               isay2_txt.visible = on;
                               show_pointer = 1; // display the mouse pointer
                               while (their_pan.visible == on) {wait (1);} // stop the enemy until this panel disappears
                               stop_player = 0; // allow the player to move again
                               // text processing ends here
                       }

 

The enemy will attack for as long as its health is positive; if the player is alive and comes closer than 2000 quants to the enemy, we increment lines_on, and if lines_on = 1 (the lines are to be said only once) we start to process the text: we set dialog_number to 7 (the dialogs from Aum 49 have ended at dialog_number = 6), we set the relevant lines of text for the monster and for the player, we display them and we wait until their_pan is made invisible; this will happen when the dialogs have disappeared from the screen. The last line of code will allow the player to move again, but only if the texts have disappeared.

 

                       if (dialog_number == 8) // the player is a chicken? then fly away
                       {
                               snd_play(startup_wav, 60, 0); // "initiating the startup sequence
                               sleep (2.5); // wait until the previous sound has stopped
                               snd_play(launched_wav, 100, 0); // play the launching sound
                               while (my.z < 50000) // start flying
                               {
                                       my.z += 300 * time; // increase enemy's z
                                       my.pan += 50 * time; // and rotate it while it is moving upwards
                                       my.scale_x -= 0.15 * time; // decrease the size of the monster (create the illusion that it shrinks faster)
                                       my.scale_y = my.scale_x;
                                       my.scale_z = my.scale_x;
                                       wait (1);
                               }
                               break; // get out of here if the monster has disappeared
                       }

 

If dialog_number = 8, meaning that the player isn't courageous (and this was decided inside panelstexts.wdl), the monster will play a (funny?) "initiating the startup sequence" sound, it will wait until the sound is over, and then it will play the launched_wav sound. The enemy will start moving upwards with a big speed, rotating around its pan angle at the same time. We decrease its scale on all the axis, creating the illusion that the monster moves away at an enormous speed (we've got a limited level size to play with). The last line of code stops the "while" loop.

 

aum50_mw1

 

       enemy_speed.x = 30 * time; // "walk" speed
       ent_move(enemy_speed, nullvector);
       ent_cycle("walk", my.skill19); // play walk frames animation
       my.skill19 += 7 * time; // "walk" animation speed
       my.skill19 %= 100; // loop the animation
       if (vec_dist (my.x, player.x) < 400) // if the player comes closer than 400 quants attack him
       {
               my.skill20 = 0; // reset the "attack" frames
               snd_play (beast2_wav, 30, 0);
               while ((my.skill20 < 100) && (player.health > 0))
               {
                       vec_for_vertex (right_fist, my, 3); // get the position of the right fist (3rd vertex in Med)
                       vec_for_vertex (left_fist, my, 138); // get the position of the left fist (138th vertex in Med)
                       if ((vec_dist (right_fist.x, player.x) < 100) || (vec_dist (left_fist.x, player.x) < 100)) // hit the player?
                       {
                               if (vec_dist(right_fist.x, player.x) < 200) // the player was hit with the right fist?
                               {
                                       player.pan -= 1; // rotate him in that direction
                               }
                               if (vec_dist(left_fist.x, player.x) < 200) // the player was hit with the right fist?
                               {
                                       player.pan += 1; // rotate him in the opposite direction
                               }

 

The code from above is executed if the player has decided to fight the monster; it moves the monster and animates it using the "walk" animation frames. If the player is closer than 400 quants, the monster will play its beast2_wav sound, and if the player is alive it will set right_fist and left_fist to the coordinates of the 3rd and 138th vertex. I could have used the same tracing method to damage the player, but I have thought that you might be interested in seeing a new method that works well for close combat. I get the position of those 2 key vertices that are placed on monster's fists, and if the distance between them and player's origin is smaller than 100 quants (experimental value) I damage the player. If the player is hit by the monster, I rotate him in the proper direction; this makes it a bit harder for the player but it's still really easy to kill that fat monster.

 

                               // the player loses health more easily if its armor is damaged (or off) and if its shield isn't used
                               player.health -= 4 * (2 + armor_damaged - shield_on) * time;
                               player.strength -= 0.7 * time;
                               if (player.strength < 0)
                               {
                                       armor_damaged = 1; // remove the body armor and make the player more sensitive to hits
                               }
                               ent_playsound (my, sword_wav, 50); // sword sound
                       }
                       ent_cycle("attack", my.skill20); // play attack frames animation
                       my.skill20 += 5 * time; // "attack" animation speed
                       wait (1);
               }
               sleep (0.1); // slows down the enemy and reduces the number of traces per second
       }
       while (their_pan.visible == on) {wait (1);} // stop the enemy until this panel disappears
}

 

The player has been hit here; we decrease its health, depending on the status of the armor and shield, and then we play the old sword_wav sound that was also used by the skeleton. The monster will play its "attack" animation frames with the speed given by 5 * time, which sets the attacking rate as well. The last line stops the monster if another dialogue appears on the screen.

 

       else // the player is farther than 2000 quants away
       {
               enemy_speed.x = 40 * time;
               my.skill47 += ent_move(enemy_speed, nullvector);
               if (my.skill47 > 400) // play with 400
               {
                       snd_play(beastwalk_wav, 100, 0);
                       if (random(1) > 0.5)
                       {
                               camera.roll = 2;
                       }
                       else
                       {
                               camera.roll = -2;
                       }
                       my.skill47 = 0;
               }
               ent_cycle("run", my.skill21); // play stand frames animation
               my.skill21 += 5 * time; // "stand" animation speed
               my.skill21 %= 100; // loop the animation
       }

 

If the player is farther than 2000 quants away, the monster will start running towards him, playing some awful beastwalk_wav sounds and making the camera shake by changing its roll angle. The last 3 lines take care of the "run" animation.

 

               vec_set(temp, player.x); // rotate the enemy towards the player all the time
               vec_sub(temp, my.x);
               vec_to_angle(my.pan, temp);
               my.tilt = 0; // set a proper "tilt" angle
               wait (1);
               camera.roll = 0;
               enemy_speed.y = 0;
               vec_set (trace_temp, my.x);
               trace_temp.z -= 10000;
               trace_mode = ignore_me + ignore_passable + use_box;
               enemy_speed.z = -trace (my.x, trace_temp.x) - 1; // place enemy's feet on the ground
               move_mode = ignore_passable + ignore_passents;
       }
       while (my.skill22 < 80) // the enemy is dead here
       {
               ent_cycle("death", my.skill22); // play death frames animation
               my.skill22 += 1 * time; // "death" animation speed
               wait (1);
       }
       my.passable = on; // the corpse can't be hit by the sword from now on
       player.experience += 15; // increase player's experience by 15
}

 

The last chunk of code makes sure that the monster faces the player at all times, waits 1 frame (this instruction is needed by the parent while loop) and then sets the movement speed on y and z (gravity). The last while loop plays the "death" animation, and then it makes the monster passable and adds 15 points to player's experience.

 

This article concludes the Morrowing series. You can continue to walk on that path, meet and (hopefully) kill the first skeleton, reaching the entrance door. I chose to make the enemies pretty weak but you can (and you should) increase their health in order to make the things more challenging for the player.

 

Morrowing was a shorter series, but I was planning to change Aum's content and layout a few months ago and Aum50 sounded like a round value to me. I'll see you in Aum51 with a new layout, new topics and - hold your breath - a searchable magazine.