Morrowing

Die letzte Morrowing Episode (*schnief*) führt einen neuen Charakter ein, der mit dem Spieler spricht und abhängig vom Gesprächsverlauf unterschiedlich reagiert: er kann fortfliegen (wörtlich!) oder mit dem Spieler kämpfen. Ich werde mich nicht auf den Code für den Dialog konzentrieren; die Funktionsweise des Dialogsystems ist in AUM 49 ausführlich erklärt.

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;

Die ersten Zeilen deklarieren einige lokale Variablen; die Wichtigsten sind right_fist und left_fist, die für den unbewaffneten Kampf benutzt werden. Das Monster hat 200 Punkte Gesundheit und ist zu Beginn unsichtbar und passierbar; es wartet bis der Spieler den Streitkolben aufhebt und anlegt (mace != null) und wird dann unpassierbar und sichtbar, indem der Alpha Wert in einer Schleife vergrößert wird. Wenn das Monster nicht mehr transparent ist, gibt es ein Geräusch von sich (beast2.wav).

  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
     }

Der Feind greift an, solange seine Gesundheit positiv ist; wenn der Spieler lebt und näher ist als 2000 Quants, erhöhen wir lines_on und falls lines_on gleich 1 ist (das Gespräch soll nur einmal stattfinden) wird der Text verarbeitet: dialog_number wird auf 7 gesetzt (die Dialoge aus AUM 49 endeten mit dialog_number = 6), wir stellen die korrekten Textzeilen für das Monster und den Spieler ein, zeigen sie an und warten, bis their_pan unsichtbar ist; dies geschieht, wenn der Dialog vom Bildschirm verschwindet. Die letzte Codezeile erlaubt dem Spieler die Bewegung, aber nur wenn die Texte fort sind.

     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
    }

Falls dialog_number == 8 ist dann heißt das, dass der Spieler zu feige war (und dies hat sich in panelstexts.wdl entschieden) und das Monster wird ein (lustiges?) “Initiating Startup Sequence”-Geräusch von sich geben, warten bis es verklungen ist und dann launched.wav abspielen. Der Feind wird sich dann mit hoher Geschwindigkeit nach oben bewegen und sich dabei um die eigene Achse drehen. Zugleich skalieren wir das Monster in allen Richtungen nach unten, um die Illusion zu erzeugen, dass es sich mit enormer Geschwindigkeit vom Spieler fortbewegt (schließlich ist die Levelgröße begrenzt). Die letzten Codezeilen beenden die Schleife.

   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
         }

Der obige Code wird ausgeführt, wenn der Spieler sich für den Kampf entschieden hat; das Monster bewegt sich und spielt dabei die "walk" Animation ab. Falls der Spieler näher als 400 Quants ist, ertönt erneut das beast2.wav Geräusch und wenn der Spieler lebt, wird right_fist und left_fist auf die Koordinaten des 3. Und 138. Vertexes gesetzt. Ich hätte die gleiche Trace-Methode für den Kampf verwenden können wie vorher, aber ich dachte, dass eine neue Art interessant sein könnte, die speziell für Nahkämpfe gut funktioniert. Ich ermittele die beiden Position der Fäuste und falls die Distanz zwischen beiden und dem Origin des Spielers kleiner ist als 100 Quants (experimenteller Wert), nimmt der Spieler Schaden. Wenn der Spieler getroffen wird, drehe ich ihn ein wenig; das macht das Töten des Monsters etwas schwerer, aber trotzdem sollte es machbar sein, den schwerfälligen Feind zu erledigen.

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

Hier wurde der Spieler getroffen; seine Gesundheit wird verringert, je nach Zustand seiner Rüsung und des Schuldes und dann verwenden wir das alte sword.wav Geräusch vom Skelett. Das Monster wird mit seinen "Attack” Frames animiert mit der Geschwindigkeit 5*time, die zugleich die Angriffsrate festlegt. Die letzte Zeile stoppt das Monster, wenn ein anderer Dialog auf dem Bildschirm erscheint.

  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
  }

Wenn der Spieler weiter als 2000 Quants entfernt ist, beginnt das Monster mit einem grauenhaften beastwalk.wav Geräusch auf ihn zuzurennen und sorgt dafür, dass die Kamera durch Änderung des Roll-Winkels wackelt. Die letzten 3 Zeilen kümmern sich um die “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
}

Der letzte Teil Code stellt sicher, dass das Monster den Spieler die ganze Zeit ansieht, 1 Frame wartet (das wird aufgrund der übergeordneten Schleife benötigt) und setzt dann die Bewegungsgeschwindigkeit in y und z-Richtung (Gravitation). Die letzte Schleife ist für die “Death” Animation zuständig und macht das Monster dann passierbar, während der Spieler 15 Erfahrungspunkte erhält.

Dieser Artikel beendet die Morrowing Serie. Sie können jetzt weitermachen, das erste Skelett treffen und (hoffentlich) bezwingen und den Eingang erreichen. Ich habe die Feinde sehr schwach gemacht, aber Sie können (und sollten) deren Gesundheit erhöhen, um dem Spieler eine größere Herausforderung zu bieten.

Morrowing war eine kürzere Serie, aber ich plante ohnehin, den Inhalt und das Layout von AUM zu ändern und Ausgabe 50 klang nach einem schönen runden Wert. Wir sehen uns in AUM 51 mit neuem Layout, neuen Thema und – halten Sie sich fest – einer Suchfunktion für das Magazin.