Years ago when I began working on my first RPG I discovered that most of the tutorials on klik sites utilized the sub-application object. Despite upgrading MMF2 and sub-app, I was getting really wonky results off of the extension. Half of the time it would work perfectly and half of the time it'd crash the game and corrupt the associated project file. No bueno; I had to figure out some other way to create a dialog system without bogging down each frame.

I'm more of a graphic designer and an artist than a programmer, so my aim was a system that was customizable, intractable, and graphically aesthetic. Of course my first thought was a huge active object with a string object on top to display text. Problem was noticeable slow-down every time the dialog was triggered because most of my level graphics were finished and the huge active object dialog background was enough to bog down performance.

What we're making:
Image
A semitransparent and repositionable dialog system which can be controlled externally via interpreted list or manually by the player.


What you'll need:
Active Shape Object - draws fill shapes with mathematical equations instead of raster graphics. Alt: Vectoral Shape Object, Active Object.
Global Store X - Creates an external INI or binary containing saved strings and variables. Alt: Custom fastloop text file interpreter, INI++, Array object.
String Parser 2 - Delimits strings by element characters. Alt: none.
Active Picture Object - Allows MMF2 to load external images at runtime. Alt: none.

Recommended:
Character Image Object - displays bitmap fonts in MMF2. Alt: Bitmap Font Object, string object, text functions in Active Shape Object.

Step One: Reinvent the Wheel.
I've played hundreds of console RPGs and although the vast majority of them are the same, a handful of games add interesting options to dialog engines like opacity, window positioning, graphic or animated avatars, typewriter text, and message review. IMO, it's best to program with a set of goals in-mind instead of quickly throwing together something basic and trying to add advanced features later. For the purposes of this tutorial, I'll show you how to add opacity, window positioning, and graphic avatars. By the end of the tutorial, other features can be added by tweaking the example scripting a bit.

Step Two: Font.
Both the Character Image Object and the Bitmap Font Object are a chore to set up. Now is a good time to figure out whether you want to use a fancy bitmap font or a simple string object. If you're using the Active Shape object and don't want image avatars, Active Shape can display text strings. If you don't want to set up a bitmap font but still want to use avatars, you'll need to use a string object because the Active Picture avatar is going to overlap the text displayed on an Active Shape by default.

Step Three: Organize the Level Editor.
Drop Global Store X, Active Shape, Character Image, Active Picture, String Parser 2, one counter, and two button objects into a blank frame.
Rename Button1 to GUItop and Button2 to GUIbottom.
Resize active shape to reflect the size of your desired dialog.
Add three alterable values to the Character Image object then set their values;
Alterable Value 1: StartLine = 0
Alterable Value 2: CurrLine = 0
Alterable Value 3: EndLine = 1

Alt: If you're using a string object instead of Character Image, these values can be added to the active shape object or any in-level active object.
Rename counter1 to SemitransValue with an initial value of 40, a minimum value of 0 and a maximum value of 128.
Lastly, we need to create one Global Value called AllowMovement then set it to 1.

If you want to follow this tutorial by the letter, here's the object dimensions I used:
Game Resolution: 640x480;
Level Resolution: 640x480;
Active Shape: 600x100;
Active Picture: 90x90
Character Image: 490z80
GUItop & GUIbottom: 16x16 bitmap pushbutton;

Step Four: Global Store X setup.
If you skipped the initial options screen when you dropped Global Store X on the level editor, that's fine. Just click it and select Settings -> Edit from the properties window. Global Store X can keep strings, values, and bools (which are specific types of values). The values and bools are probably useful to store other game variables, but we're only going to need a bunch of available string slots for this tutorial. Anyway, go ahead and set the String Array size to 20 for now; that will allow 20 dialog texts - good enough for this tutorial, but you'll want to increase the value when applied to a real game project.

Step Four and a Half: Global Store X INI setup.
To create an INI and start adding content for our dialog engine to display later, we need to tell MMF to add at least one string and then save the INI. Go into the event editor and add this event scripting:
Start of Frame:
GlobalStoreX -> Add string 0 TEST.
GlobalStoreX -> Save all INI -> Expression -> Apppath$+test.ini in group dialog.

Now save the project and playtest it. End the playtest and check your project directory for test.ini. If it's there, you can now delete the start of frame event we just created.

Step Four Point Seventy-Five: Add dialog.
Now open your test.ini file in notepad. You should see something like this:
[dialog]
NoOfStrings=20
Base=0
0=TEST
1=
2=
3=
4=
...

It's time to start adding dialog, BUT we should consider a few things beforehand. We're not just loading text into the dialog engine. We also need to devise some kind of way to tell the Active Picture object to load the correct avatar and figure out some way to trigger window positioning and opacity. Normally, this would mean three extra text files and a lot of event scripting to ensure all four files are synchronized and read at the same time. Mmm... pass. Instead, we're going use StringParser2 to separate several strings in each line of the INI.
On line 0, try something like this:
1|1|40|First line of dialog!
I know that doesn't make much sense. Here's the breakdown.
Avatar|Window_Position|Opacity|Text_Message
I still need to do some explaining. The Avatar setting is a number because if the avatar slot = 1, the dialog engine will load 1.png. If the number is 2, the engine will load 2.png.
Window_Position determines whether the dialog will appear at the top or bottom of the screen. 0 = top and 1 = bottom.
Opacity sets the semitransparency value of the Active Shape and Active Picture objects between a value of 1 to 128.
Keep in mind that none of this is going to work until we set up StringParser2 to interpret these values. Modifying how these values work is as easy as changing the way they're interpreted. Anyway, go ahead and fill all 20 lines with similar dialog. If you're still unsure about all of the interpreted values, keep them set at 1|1|40|Text on each line. We can always tweak them later.

Step 5: Create Avatars.
Go back to the level editor and select the Active Picture Object. Make sure it's sized correctly to fit in the dialog object, then take note of Active Pictures height and width values. Now we need to create a couple of image avatars. Doesn't need to be anything fancy, but each avatar should be the exact same resolution and size of the Active Picture Object. When you've made a couple of images, save them as 1.png and 2.png into the project directory. Eventually you'll want to make more than two and probably set up an avatar directory, but for the purposes of this tutorial it isn't necessary.

Step 6: The Event Editor (Finally!).
At the very start of each frame, we need to correctly size and position all of the dialog objects then hide them, load the INI into GlobalStoreX, and add a delimiter character to StringParser2.
Start of Frame:
Active Shape -> Set Size (600x100); Set Xpos to 20; Set Ypos to 360; make invisible.
Character Image -> Set Page Width = Width(ActiveShape)-110; Set Page Height = Height(ActiveShape)-20; Set position at (100,10) from ActiveShape; Make invisible.
Active Picture -> Set Xpos = X(ActiveShape)+5; Set Ypos = Y(ActiveShape)+5; Make invisible.
GUItop -> Set Xpos to X(ActiveShape)+Width(ActiveShape)-17; Set Ypos to Y(ActiveShape)+1; Make object invisible;
GUIbottom -> Set Xpos to X(ActiveShape)+Width(ActiveShape)-17; Set Ypos to Y(ActiveShape)+Height(ActiveShape)-18; Make object invisible;
GlobalStoreX -> Load strings from Apppath$+test.ini, group dialog;
StringParser2 -> Add delimiter |;


Let's talk about that AllowMovement global value. Normally characters aren't allowed to walk all over the place while game dialog is happening. When AllowMovement = 1, the player should be allowed to walk about and explore, but when AllowMovement = 0, it means there's something going on which should restrict the player from moving like dialog or a cutscene. As long as we can agree that NO DIALOG will take place when the player can move, the AllowMovement value can be changed to 1 when we want to end text dialog and allow the player to move again. We haven't even touched on creating a character or a movement engine, but it shouldn't matter until this dialog example is nearly complete.

Create a new group of events titled TextGUI and make sure it IS NOT active on frame start. Until otherwise noted, all the event scripting below should be placed in the TextGUI group. Keep in mind that StringParser2 treats all elements as a text string, so any time we need to pull a value from a StringParser2 element, we have to use the Val() modifier so MMF2 converts the string 1 into the number 1. I know this is a little hard to read, but the event is triggered by setting CharacterImage's StartLine and EndLine alterable values, pulling the StartLine dialog from GlobalStoreX, reading the avatar file number and loading it into ActivePicture, reading the position data to know whether to position the dialog at the top or bottom of the screen, and setting opacity on both ActiveShape and ActivePicture.
GlobalValue AllowMovement = 0 + Only one action when event loops:
StringParser2 -> Set source string GetString$(GlobalStoreX), StartLine(CharacterImage);
Counter -> Set counter to Val(listGetAt$(StringParser2),3));
ActivePicture -> New picture: Apppath$+listFirst$(StringParser2)+.png;
CharacterImage -> Set text to listLast$(StringParser2);
ActiveShape -> Reappear;
ActivePicture -> Reappear;
CharacterImage -> Reappear; Bring to front;


This is kind of unnecessary event scripting. I think I took the cheap way out copy/pasting all the positioning data over and over again when I could have simply set up a few extra variables to reference and switch between. I'm sure someone could optimize this so it makes a little more sense. Basically, it just tests the converted value from the second element in StringParser2 to determine whether the dialog window should appear at the top or bottom of the screen repositioning all dialog elements.
Compare 2 General Values Val(listGetAt$(StringParser2),2))=1 + Only one action when event loops:
ActiveShape -> Set Xpos to 20;
ActiveShape -> Set Ypos to 20;
CharacterImage -> Set position at (100,10) from ActiveShape.
CharacterImage -> Set page width to Width(ActiveShape) - 110;
CharacterImage -> Set page height to Height(ActiveShape) -20;
ActivePicture -> Set Xpos to X(ActiveShape)+5;
ActivePicture -> Set Ypos to Y(ActiveShape)+5;
GUItop -> Set Xpos to X(ActiveShape)+Width(ActiveShape)-17;
GUItop -> Set Ypos to Y(ActiveShape)+1;
GUIbottom -> Set Xpos to X(ActiveShape)+Width(ActiveShape)-17;
GUIbottom -> Set Ypos to Y(ActiveShape)+Height(ActiveShape)+18;
GUItop: Make object invisible;
GUIbottom: Make object visible;

Compare 2 General Values Val(listGetAt$(StringParser2),2))=0 + Only one action when event loops:
ActiveShape -> Set Xpos to 20;
ActiveShape -> Set Ypos to 360;
CharacterImage -> Set position at (100,10) from ActiveShape.
CharacterImage -> Set page width to Width(ActiveShape) - 110;
CharacterImage -> Set page height to Height(ActiveShape) -20;
ActivePicture -> Set Xpos to X(ActiveShape)+5;
ActivePicture -> Set Ypos to Y(ActiveShape)+5;
GUItop -> Set Xpos to X(ActiveShape)+Width(ActiveShape)-17;
GUItop -> Set Ypos to Y(ActiveShape)+1;
GUIbottom -> Set Xpos to X(ActiveShape)+Width(ActiveShape)-17;
GUIbottom -> Set Ypos to Y(ActiveShape)+Height(ActiveShape)+18;
GUItop: Make object visible;
GUIbottom: Make object invisible;


This event allows dialog to be advanced by pressing the Z key. The one real glitch I've encountered making dialog engines is that I can never use the same key to trigger dialog as I do to advance dialog text. Using the same key usually results in an infinite dialog loop.
GlobalValue AllowMovement = 0 + Upon pressing 'Z':
CharacterImage -> Add 1 to CurrentLine
StringParser2 -> Set source string to GetString(GlobalStoreX), CurrentLine(CharacterImage);
ActivePicture -> New picture: Apppath$+listFirst$(StringParser2)+.png;
CharacterImage -> Set text to listLast$(StringParser2);


MOST IMPORTANT PART. As the player presses Z to continue through dialog, when CurrentLine is greater than EndLine it means the player has scrolled through each of the determined dialog lines and the dialog should close. The easist way to close the dialog is to allow the player to move again.
Compare 2 General Values: CurrentLine(CharacterImage) > EndLine(CharacterImage):
Global Values -> Set AllowMovement to 1;


The following events handle manual opacity modification. If the player has trouble reading text because the opacity value is set too low in test.ini, they can use the + and - keys to manually increase and decrease opacity.
Repeat while + is pressed:
SemitransValue -> Add 3 to counter;
ActiveShape -> Set semitransparency to value(SemitransValue);
ActivePicture -> Set semitransparency to value (SemitransValue);

Repeat while - is pressed:
SemitransValue -> Subtract 3 from counter;
ActiveShape -> Set semitransparency to value(SemitransValue);
ActivePicture -> Set semitransparency to value (SemitransValue);


Window positioning is set in test.ini, but the user should be able to customize this as well - you know, just in case there's action happening on-screen the dialog accidentally overlaps. These events just handle all the dialog repositioning when players manually click the GUI placement buttons.
Button GUItop is clicked:
ActiveShape -> Set Xpos to 20;
ActiveShape -> Set Ypos to 20;
CharacterImage -> Set position at (100,10) from ActiveShape.
CharacterImage -> Set page width to Width(ActiveShape) - 110;
CharacterImage -> Set page height to Height(ActiveShape) -20;
ActivePicture -> Set Xpos to X(ActiveShape)+5;
ActivePicture -> Set Ypos to Y(ActiveShape)+5;
GUItop -> Set Xpos to X(ActiveShape)+Width(ActiveShape)-17;
GUItop -> Set Ypos to Y(ActiveShape)+1;
GUIbottom -> Set Xpos to X(ActiveShape)+Width(ActiveShape)-17;
GUIbottom -> Set Ypos to Y(ActiveShape)+Height(ActiveShape)+18;
GUItop: Make object invisible;
GUIbottom: Make object visible;

Button GUIbottom is clicked:
ActiveShape -> Set Xpos to 20;
ActiveShape -> Set Ypos to 360;
CharacterImage -> Set position at (100,10) from ActiveShape.
CharacterImage -> Set page width to Width(ActiveShape) - 110;
CharacterImage -> Set page height to Height(ActiveShape) -20;
ActivePicture -> Set Xpos to X(ActiveShape)+5;
ActivePicture -> Set Ypos to Y(ActiveShape)+5;
GUItop -> Set Xpos to X(ActiveShape)+Width(ActiveShape)-17;
GUItop -> Set Ypos to Y(ActiveShape)+1;
GUIbottom -> Set Xpos to X(ActiveShape)+Width(ActiveShape)-17;
GUIbottom -> Set Ypos to Y(ActiveShape)+Height(ActiveShape)+18;
GUItop: Make object visible;
GUIbottom: Make object invisible;


We're finally done with the TextGUI group! Running the game at this point still won't do a damn thing because we don't have a player character or a trigger event, so let's create those. The player can be an active set to 8-direction movement - nothing special. To create the trigger, make a new active object; name it 'DialTrig0001'. Create two alterable values; name the first 'DialStartLine' and the second 'DialEndLine'. DialStartLine should be the first line of text from test.ini that you want shown and DialEndLine should be the last line shown before the dialog closes. Now let's go back into the event editor.

Outside of the TextGUI group, create a new event.
Global Value: AllowMovement=1 + Player is overlapping DialTrig0001 + Upon Pressing 'Space Bar':
CharacterImage -> Set StartLine to DialStartLine.
CharacterImage -> Set CurrentLine to DialStartLine.
CharacterImage -> Set Endline to DialEndLine.


That's it. Just save and playtest for dialog awesomeness. Once your game is developed, you can open and re-save the test.ini file as a binary file using GlobalStoreX so your players can't hack all the game dialog. I hope the tutorial was relatively easy to follow along with, but I'm pretty sure it wasn't. If anyone can recommend optimizations, please do. I will try to follow up with another dialog tutorial article that achives as close to the same effect as possible without using extensions.