Morrowing

This month I have finished the dialogue system and I have created an npc character that discusses with the player. Other features were added as well: a fire effect with smoke, proper-scaled models, and so on.

Let's start with the fire effect:

action my_fire
{
my.invisible = on;
my.passable = on;
while (1)
{
vec_set(temp.x, my.x);
vec_add(temp.x, vector(random(20) - 40, random(20) - 40, random(15)));
effect (fire_effect, 30 * time, temp.x, normal);
if (random(1) > 0.9)
{
temp.z += 70 + random(30);
effect (smoke_effect, 1, temp.x, normal);
}
my.pan += 1 * time;
my.lightrange = 200;
my.lightred = 200;
my.lightgreen = 150 + random(50);
my.lightblue = 100;
wait (1);
}
}

The entity that has this action attached to it will become invisible and passable; we generate particles using the fire_effect() function at the position given by the invisible entity + or - up to 20 quants on the x and y axis, and up to 15 quants higher on the z axis. If random(1) is greater than 0.9 we generate a smoke particle 70... 100 quants above the invisible entity. The last few lines of code make the entity generate dynamic light on a range of 200 quants, using a yellowish color.

function fire_effect()
{
temp.x = random(20) - 10;
temp.y = random(20) - 10;
temp.z = random(30);
vec_add (my.vel_x, temp);
my.alpha = 20 + random(80);
my.bmap = fire_tga;
my.size = 100;
my.bright = on;
my.move = on;
my.lifespan = 4;
my.function = fade_flames;
}

function fade_flames()
{
my.alpha -= 1 * time;
if (my.alpha < 0) {my.lifespan = 0;}
}

function smoke_effect()
{
temp.x = random(1) - 0.5;
temp.y = random(1) - 0.5;
temp.z = random(2);
vec_add (my.vel_x, temp);
my.alpha = 10 + random(20);
my.bmap = smoke_tga;
my.size = 100;
my.bright = on;
my.move = on;
my.lifespan = 350;
my.function = fade_smoke;
}

function fade_smoke()
{
my.alpha -= 0.2 * time;
if (my.alpha < 0) {my.lifespan = 0;}
}

The functions used for the fire and for the smoke are almost identical; the particles have a random speed on x and y and a positive speed on the z axis (that's what moves them upwards). The smoke and the fire will fade away by decreasing their alpha.

Let's start discussing about the npc (non-playing character) code; I had to create some sort of a trigger that tell the npc if the player has entered the tent or not.

var player_entered_tent1 = 0;

action tent1_entrance
{
my.invisible = on;
my.passable = on;
while (player == null) {wait (1);}
while (vec_dist(player.x, my.x) > 300) {wait (1);} // wait until the player has come close to the entrance of the first tent
player_entered_tent1 = 1;
}

I have placed an invisible entity at the entrance of the tent; it waits until the player comes closer than 300 quants to it, and then it sets the variable named "player_entered_tent1" to 1.

action darth_vader
{
var saw_player = 0;
while (player == null) {wait (1);}
while (1)
{
ent_cycle("crstnd", my.skill40); // play stand frames animation
my.skill40 += 2 * time; // "stand" animation speed
my.skill40 %= 100; // loop the animation
if (player_entered_tent1 == 1)
{
player_entered_tent1 += 1; // make sure that this "if" branch is executed only once
// text processing starts here
dialog_number = 2; // talking to darth vader
str_cpy(temp_str, line4_str); // that's the "Look! I am your father!" line
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 (1); // wait for 1 second
isay1_txt.string = line5_str; // that's the "Really? I'm not so sure about that..." line
isay2_txt.string = line6_str; // that's the "Oh, father! I finally get to meet you!"
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
sleep (1);
while (their_pan.visible == on) {wait (1);} // stop the action until this panel disappears
stop_player = 0; // allow the player to move again
// text processing ends here
}
wait (1);
}
}

The code for our npc is simple; it plays the "crstnd" animation in a loop and waits until the player has entered the tent. As soon as that happens, player_entered_tend is incremented, making sure that the "if" branch is executed only once. Please examine the "// text processing starts here" and "// text processing ends here" comments; this is the type of code that needs to be added to your characters if you'd like them to be able to communicate with the player using my code. The snippet between the 2 comments sets us the first line spoken by Darth Vader, as well as player's possible answers to it. I have used a variable named "dialog_number" which counts the number of dialogs; I have set dialog_number to 1 for the first dialog (the player talking to the skeleton at the beginning of the level), dialog number = 2 for the first dialogue between Darth Vader and the player, and so on. You can set any dialog_number value, as long as you use the same value in the panelstexts.wdl file (more on that a bit latter).

Let's discuss the code a bit: we copy the content of the line4_str inside temp_str, and then we run the process_their_strings() function, which splits the text on 1 or 2 lines, depending on its size. Check Aum47 if you have forgotten how that function works; the processed string is stored inside the their_lines_str, and it is displayed on the screen using the theysay_txt text. Ok, so at this point we have displayed the "Look! I am your father!" line at the top of the screen; take a look inside the lines.wdl file to see all the lines.

We set stop_player to 1; this will stop the player, and then, after a second, we display the texts for player's possible answers: "Really? I'm not so sure about that..." and "Oh, father! I finally get to meet you!". These lines are displayed by isay1_txt and isay2_txt, which are made visible, and show_pointer = 1 displays the mouse pointer. The action will stop for as long as the player discusses with the npc (their_pan.visible = on); as soon as that panel has disappeared, stop_player will be set to zero, allowing the player to move again.

The bottom line is this: you only have to set the first npc line and the first answers inside the action that is attached to the entity; all the other lines are taken care of inside the panelstexts.wdl file and the player will choose the desired line by clicking a button on a panel. I am using the choose_answer() function inside the panelstexts.wdl file:

function choose_answer(number)
{
if (number == 1) // the player has selected the first line (has clicked the first button)
{
if (dialog_number == 2) // the player has picked the "Really? I'm not so sure about that..." line
{
str_cpy(temp_str, line7_str); // that's the ""You KNOW that! What does your heart tell you?" line
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 (1); // wait for 2 seconds
isay1_txt.string = line8_str; // "My heart tells me that you are a weird sci-fi movie character!"
isay2_txt.string = line9_str; // "I have waited for this moment for so long!"
dialog_number = 3;
}
if (dialog_number == 1) // the player has picked the "I am The mighty Guard..." line
{
isay1_txt.visible = off;
isay2_txt.visible = off;
theysay_txt.visible = off; // hides their_pan as well -> resumes the gameplay
show_pointer = 0; // hide the mouse pointer
}
}

I am discussing the code for the first 2 dialogs (dialog_number = 1 and dialog_number = 2); panelstexts.wdl contains 6 dialogs but they are similar to the ones that I'm going to discuss. If (number == 1) means that the player has selected the first answer (has clicked the first button on the panel at the bottom of the screen). Please note the order of the dialogs; I start with bigger dialog_number values and I go down to dialog_number = 1, making sure that only the desired "if" branches are executed when we press one of the buttons. Dialog_number = 1 means that the player talks to the skeleton that appears at the beginning of the game; the player has picked the first line:

We have decided that the player must be able to run away if he or she selects this line, so we hide the his / her lines, we hide skeleton's line and then we hide the mouse pointer. If dialog_number = 2, the player is discussing with the npc from inside the tent.

The first line for the npc, as well as player's lines, were set by the action that is attached to the npc, remember? We need to do that because these lines must be triggered by the npc. If the player presses the highlighted answer, as indicated in the shot above, the following screen will be displayed:

How did that happen? Let me copy and paste the corresponding piece of code:

if (dialog_number == 2) // the player has picked the "Really? I'm not so sure about that..." line
{
str_cpy(temp_str, line7_str); // that's the ""You KNOW that! What does your heart tell you?" line
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 (1); // wait for 1 second
isay1_txt.string = line8_str; // "My heart tells me that you are a weird sci-fi movie character!"
isay2_txt.string = line9_str; // "I have waited for this moment for so long!"
dialog_number = 3;
}

Dialog_number was set to 2 by the npc, so I have chosen the correct "if" branch; it copies the 7th line inside temp_str, it processes the text, arranging it on the screen, and then it makes it visible. We stop the player, we wait for a second, and then we display its answers (line8 and line9). Finally, we set dialog_number to 3, which will bring up the following screen if the player chooses the first line:

That's what happens if the player clicks the first button (number = 1) on its panel. Let's see what happens when the player chooses the second button:

if (number == 2) // the player has selected the second line (has clicked the second button)
{
if (dialog_number == 2) // the player has picked the "Oh, father! I finally get to meet you!" line
{
str_cpy(temp_str, line10_str); // "Listen to me, son! Take the body armor..."
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 (1); // wait for 2 seconds
isay1_txt.string = line11_str; // "Thank you, sir! I'll miss you for sure!"
isay2_txt.string = line12_str; // "Good bye. And thank you for the gadgets!"
dialog_number = 4;
}
if (dialog_number == 1) // the player has picked the "All of the sudden..." line
{
exit; // shut down the engine
}
}
}

If dialog_number = 1, the player is talking to the skeleton, and it has picked the highlighted (red) line:

If this is the case, the code will simply shut down the engine. If dialog_number = 2, the player is talking to the npc, and it has picked the highlighted line, as shown below:

Clicking the second button will copy and process the 10th line inside lines.wdl, making it visible, and then it will set line11 and line12 as possible answers for the player. Finally, setting dialog_number to 4 allows the dialog to continue on a "if (dialog_number == 4)" branch.

That's how it works! You can put anything inside the "if" branches and have the npc attacking the player all of the sudden, or you could trigger an explosion, and so on. Don't forget to display the first npc line and player's possible answers using the action that is attached to the npc, and then process all the other text inside the choose_answer(number) function. Oh, and don't forget to set a different dialog_number for every screen that contains a dialogue.