Dynamic content

With this tutorial you are going to learn how to create dynamic objects and interactive gameplay content. We are going to cover the basics of dynamic content, and also spill out some more advanced suggestions about how some things should be done to keep your Finite State Machine (FSM) scripts and all the complex machines maintainable. We are also going to create a door and turn it into a "prefab" that can be easily copied around. Download the example level. You can also download some prefabricated dynamic objects by Remedy.

FSMs, Messages and the hierarchy

The basis of all the dynamic content are so-called Finite State Machines (FSM). FSM scripting is especially evident in Max Payne because there are no pre-made dynamic entities such as "a door" or "a lift" that you could use, but rather all the dynamic content is created with dynamic objects (DO's) and few other simple entities like triggers. These entities can detect various events and send messages to each others to control all the things you see happening in the game. For example when the player opens a door, it can send a message to, say, a huge boulder to make it roll towards the player, and when the boulder crashes into something, it can send messages to the nearby enemies to make them come and investigate all the noise. As you will learn in this tutorial, it's a flexible system and can be used to construct pretty much anything you can think of.

Basic message can look like this:

::Cellar_Room2::Enemy1->C_SetStateMachine( CrouchAndShoot );

It consists of the receiver "::Cellar_Room2::Enemy1", the message itself "C_SetStateMachine", and finally the possible parameters "CrouchAndShoot".

Other examples:

::Room2::Door1->DO_StopAnimation();
parent::Enemy4->C_GoTo( ::Street1::Waypoint1,1);
Button->A_Play3DSound( dynamic, switch ,"" );
this->C_PickUpWeapon( deserteagle );

If messages point to objects that are inside MaxED, they require the hierarchy to be defined for the receiver. You can use absolute hierarchy or  relative hierarchy from the message sender.

Example: Object A is a top-level object. It has children B and C, and B has child D.


A simple hierarchy example

The fully qualified names are:

A         ::A
B         ::A::B
C         ::A::C
D         ::A::B::D

When you define relative names, you have keywords "this" and "parent" at your disposal. An object can refer to itself by "this", and to its parent with "parent". An object can refer to its child directly by using the child's name.

Object Can Refer to: With relative name
A A
B
C
D
this
B
C
B::D
B A
B
C
D
parent
this
parent::C
D
C A
B
C
D
parent
parent::B
this
parent::B::D
D A
B
C
D
parent::parent
parent
parent::parent::C
this

About whether you should use absolute or relative names, it really depends on the situation. Usually when you create an enclosed machine, such as a door, all the messages that the door sends to the different parts of itself should be relative to ensure that when you copy the door around, the copy will still have its messages defined correctly (=The new copy won't send messages to the original door).

But then again if the machine sends messages outside of itself, for example if the door sends a message to an enemy somewhere else in the level, you might want to use absolute naming, which will ensure that the new copies of the door will do all the same things as the original. (=All the copies of the machine will send the same messages outside themselves)

In addition to these, there's two more keywords that you can use some situations as the receivers; "Activator" and "Player". For example a collision trigger can send messages to the Activator when it's been touched, and some messages can be sent simply to the player (The character that represents the player).

activator->C_SetHealth( 0 );
player->A_StopAll3DSounds( head );

As you can see, there's no hierarchy for these receivers. Likewise there's no hierarchy involved if the entities are sending messages to the game modes, for example to maxpayne_gamemode for changing the level, or maxpayne_hudmode for hudprint, or x_modeswitch to perform a modeswitch (between game, menus and graphic novels).

maxpayne_gamemode->gm_init(The_Next_Level):;
maxpayne_hudmode->mphm_printdirect("Don't play with guns");
x_modeswitch->s_modeswitch(game);

The complete list of messages can be found from the MaxFX-Tools Help.

The dynamic entities and their purposes

Here is a list of entities that you can create in MaxED and use as a part of your FSM scripting. You can create any of these entities by pressing N in F3 mode.

Waypoint - This entity is used for AI scripting. Enemies can be commanded to move to waypoints.

Jumppoint - This is the entity where player spawns to. The level can include multiple jumppoints, and you can jump between them with insert and delete in the game, if developermode is on. The initial jumppoint is defined in levels.txt. The level won't run in normal mode if it isn't defined, although in developer mode you will just get an error message about it.

Enemy - A character, usually an enemy, although due to the flexible nature of the AI scripting, any enemy can be used as a non-harmful NPC. You can select what type of an enemy or NPC you want this to be from the properties of the entity. (Press enter in F5 mode or click RMB to it in the hierarchy tree and select "Properties".)

Level item - Any item you can find in the game, like ammunition, weapons or painkillers. You can choose the type of the level item from the properties.

Player collide trigger - A spherical trigger that activates when the player touches it. You can change the radius from the properties.

Character collide trigger - A spherical trigger that activates when any character (including the player) touches it. You can change the radius from the properties.

Projectile collide trigger - A spherical trigger that activates when any damage-causing projectile touches it. You can change the radius from the properties.

Action button trigger - A trigger that has to be used by the player in order for it to activate.

Look-at trigger - A trigger which will cause the player's character to look towards its center when player is inside it's radius.

Floating FSM - An invisible entity that can be used as a part of the FSM logics of a system or as an emitter for sounds and particle effects. It's basically an all-around relay entity for messages.

Pointlight - A Gouraud light for characters and the desired dynamic objects. The light itself is static in the world and cannot be moved. More about it in Dynamic lighting section of the advanced lighting article. (Note that you can make moving dynamic lights that affect the geometry by using such a particle effect)

Creating DOs

In addition to the entities listed above then there's so called dynamic objects, which make up a big portion of all the dynamic content . These are basically meshes that have some functions. All the doors, cars, crates, lifts and choppers you see in the game are dynamic objects which have different FSM logics and animations put in by the mapper. As mentioned before, there is no separate entity for "Door" or "Car", everything is the same. A breakable crate is a dynamic mesh which hides itself and creates a particle- and sound effect when it's hit. A chopper is a dynamic mesh which has lots of invisible parents that animate and move the chopper in the sky in a believable manner. Any model that moves or interacts with something in the game is a dynamic object.

Then into the creation of DO's. Let's try and make a breakable window. First we need some sort of a level to play with. Just create two rooms with a window opening between them (and a 1.5m x 2.5m doorway), and create a flat polygon there to act as the glass for the window (You can get the materials from the example level of this tutorial). Create a flat polygon by drawing the shape in F3 mode onto the grid - just like you would create any object - but hold down shift when pressing RMB.


When you import the materials from the example level, click on the glass material in the material window with RMB and make sure Dualsided and Alpha test material flags have been set.


If it happens that you can't see the glass in the editor when it's Alpha test, check MaxED's "Local Preferences". There's a value called "AlphaTestReference", change it to, say, 128 (restart of MaxEd may be needed for this to take effect). This will change the way the material is rendered in MaxED, but don't forget that the game engine itself will draw Alpha test materials differently, so don't worry if it doesn't look very good in the editor.

The glass polygon is still static and doesn't really do anything special yet, except let the bullets go through it because of the material category properties. Let's turn the object dynamic by selecting it in F5 mode, and pressing D. The same button also turns dynamic meshes into static ones. MaxED asks if you want to keep the lightmaps of an object when you turn it dynamic. Let's answer yes. DO's generally look better with lightmaps.

Setting DO properties

Now let's check out the rest of the object properties for dynamic objects. Just like with any object's properties, select the object in F5 mode, and press Return. You can rename the object now into whatever you desire, and under the Statistics tab there's some flags for us to check. Something like this:

Explanations:

Export geometry - This defines whether or not to export the geometry of the object to the .LDB file. By disabling this flag you can, for example, keep pieces of furniture around the .LVL -file while they won't get exported to the game. This flag is enabled by default.

Export lights - When you make a polygon emit light (other than black), a dynamic cone-like light (that affects only characters and the desired DO's) will also be created onto it. With this flag, these lights can be excluded or included to the export. These cone-like lights are however slower in runtime than pointlights, so it is suggested you use them instead. You can see the dynamic lights by selecting View > Visualize lights. This flag is disabled by default. 

Cast No Shadows - Even if an object has lightmaps of it own, it can be made not to cast any shadows. This should be set on any DO's that move or disappear at some point (Doors, breakable crates, moving vehicles). Disabled by default. All objects will cast shadows in preview rendering.

Insert into collision BSP - This affects only static objects. A static object can be excluded from the collision BSP by this. If you make very small objects with lots of faces, it is usually a good idea to set this flag. It's good speed wise, and also in many situations it makes a smoother gaming experience when you don't get stuck onto all the little things lying on the floor. Enabled by default.

Block explosions - This defines whether or not the DO blocks explosions. Big things like doors should block the explosions, but small stuff like little bottles shouldn't. Also if an object is blocking explosions, it will also block them from itself, thus not getting any damage from explosions. Enabled by default.

Bullet collisions - You can set the bullet collisions with this flag. Disabled by default.

Dynamic collisions - You can set all the other collisions but bullet collisions with this flag. Disabled by default.

Lightmapped  - This defines whether or not the DO uses lightmaps. If it doesn't use lightmaps, it is lit merely by dynamic lights.

Pointlights affect - With this flag you can make the dynamic lights to affect the DO even if it uses lightmaps. This is a bit of a hack and it's not recommended to be used often.

Cont. upd. - Continuous update, If the object is animating, setting this flag will make the object animate all the time even when it's not in the view cone. This flag doesn't have any effect on non-animating DO's.

TIP: You can make all the names of the DO's visible in the rendering window by selecting View > Dynamic Object Text Modes > Name

Finite state machine scripting

By finite state machine scripting, we mean putting in the messages that link the entities together. Or as it is in the case of our example, the breakable glass, putting in messages that the glass sends to itself. We are going to add messages to make the glass disappear and to play some breaking particle effects. First select the glass, and press 4 to bring up the FSM dialog.

In this first dialog we can add states to the object. The FSM scripts we need for a breakable glass won't require any states, so we won't add any. We can also bring up the Custom strings dialog from here, but we won't need any customs strings now either. Let's just press Messages-button.

The tabs you can see in the upper part of this dialog are events that can occur to the object. In this case there's 5 of them:

DO_BulletCollides - Send messages if a bullet collides the object, or it receives damage by explosion.

DO_CharacterCollides - Send messages if a character collides the object.

DO_MovedToInvalidPosition - Send messages if the object is animating and something blocks it's path.

DO_OnDeath - Send messages when the hitpoints of the object are depleted.

Startup - Send messages at startup.

There's three similar message boxes in all the tabs. The first box is for messages which are to be sent first if the given event occurs. The second box is for messages that are sent only if the DO is in a specific state when the even occurs. The third box is for messages which are to be sent after the state-specific messages (Sometimes the order does matter). Within the message boxes, the messages are also sent in the order they are listed, from up to down. You can change the order of the messages with PageUp and PageDown. Try adding some messages into the message boxes just to see how it works. For example this->do_stopanimation(); or this->a_stopall3dsounds("");

You can add messages from the Add-buttons. You can delete messages by selecting them from the message boxes, and pressing Delete. You can cut, copy and paste messages directly from and into the message boxes with CTRL-X, CTRL-C and CTRL-V.

Note however that you can't add any messages to the state-specific message boxes before you have some states defined.

Notice also that pressing TAB completes object and message names up to as far as they logically can be completed. Example: you're editing a new message on an object with a child called "the_door". Typing in "t" and pressing tab will complete the line to read "th" (because after that the strings "this" and "the_door" differ). You get the picture.

Ok, after you've got the hang of it, delete all the messages you added, and let's make the glass breakable. We can make it break by first hit, in which case we'll simply use DO_BulletCollides -tab. When the glass breaks, we want it to disappear, make a breaking sound, and emit a particle effect. Let's add these messages:

About the messages we used; DO_Hide(true); obviously hides the receiver. DO_Hide(false); would unhide it. 

A_Play3DSound plays the desired sound from the receiver. Breakable is the sound category here, and glass_shatter is the sound block inside the category. You can see all the sounds there is at your disposal by extracting the Max Payne .RAS files with RASMaker, and browsing your way to the Max Payne database sound directory ...\data\database\sounds\. There's multiple .txt files in there, which all represent a sound category. Opening "breakable.txt", you will find sound blocks, firstly "videotape_break", and also somewhere in there "glass_shatter". The third parameter for this message is the bone where to emit the sound from. It makes a difference only for characters. For DO's, where there are no bones (only characters have bones), we just leave it empty with quotation marks.

PS_StartEffect emits a particle effect from the object. There are no categories there, just the name of the particle effect. You can see the names of all the particle effects from the Max Payne database once again ...\data\database\particles\particles.txt.

Particle effects are always emitted from the center of the object, and their orientation is adopted from the orientation of the object's pivot point. There's no way of directly seeing in the editor what is the internal orientation of the given particle effect though, so that is just something you need to know or try out. For most effects in Max Payne database, if there's a direction for a particle effect, it's towards the Z-axis. You can see the pivot point of a DO by selecting it in F5 mode. The yellow line is the Z-axis. Now, in order the glass_shatter effect to have a correct orientation, let's set the pivot point of the glass to a correct orientation. We need to have the Z-axis pointing straight outwards from the surface normal. Align the grid on the surface of the glass, and move it in F12 mode so that the grid center is positioned on the glass surface. The origin of the grid shows its axis's, and now you can see that the yellow line is pointing straight outwards from the glass surface. Then go to F5 mode, select the object, and press P. This will make the object to adopt the pivot point from the grid.

Hopefully that was not too confusing. Now we get to try out our level, so export the level and load it up to the framework.

At this point, we should see some tips about exporting the levels. Generally when doing dynamic content, you need to export often, and you want to make it as easy to you as possible. Firstly, you should run the game with command line options as below:

maxpayne.exe -developer -developerkeys -screenshot -window -nodialog -skipstartup

This will make the game run in developer mode with developer keys, it will be windowed, and it will skip the startup dialog and the video. Also if you are running Windows2000, you want to make a .bat file that runs Max Payne as below:

start /low maxpayne.exe -developer -developerkeys -screenshot -window -nodialog -skipstartup

This will make the game run in the lowest priority setting automatically always when you run it, thus when the game is on the background, it won't take any resources from MaxED. Although depending on your video hardware and drivers, you might not be able to run MaxED and Max Payne simultaneously, or at least either one of them can be very prone to crash. Using low color depths to save video memory might help somewhat.

Also it is sometimes a good idea to add your level to the menu.txt so that you can conveniently load it up through the menu (Take a look at menu.txt of the BasicRoomExample that came with the game).

Now when you get the game running, you can try shooting the window and it should break. Needless to say, it's usually a good idea to quicksave as soon as the level has loaded up if you are testing some dynamic content. 

If you still want to play around with the window, you can try adding hitpoints to it. Add this->DO_SetHitpoints(15); to it's StartUp tab, and then move all the messages from it's DO_BulletCollides tab to  DO_OnDeath tab. This is a special event which is triggered only when a DO has hitpoints and they are depleted in startup. Obviously you can add hitpoints to an object upon any event that occurs in the level, not just at its own startup.

Another DO example, creating a door

Next let's create a functional door. First, if you didn't make a doorway to your level yet, let's do that. Simply Boolean a hole between the two rooms. Do not forget though that you can't boolean two rooms  together if they are sharing an exit, so if you made an exit to the window opening, you need to delete it (in F4 mode) before you can Boolean another hole between the rooms. In Max Payne the standard doorway size is some 1.5 x 2.5 meters.

Model a door for the doorway, and turn it dynamic by pressing D. Then check the object's properties, and make sure it's got the following flags enabled.

Now it might be a good idea to render lightmaps onto the door by selecting it in F5 mode and pressing R. Just make sure you don't have Hide other objects enabled in the preferences, or  the door won't take any light sources into account. In general you should relentlessly exploit the ability to render lightmaps on single objects or single rooms at a time, and fix the possible anomalies by copy/pasting lightmaps.

Now you should be seeing something like below, and we can start adding functionality to our door.

Do notice that there is countless ways of making doors in MaxED. You can make sliding door (the easiest door type) or you can make regular doors that rotate open. The doors can be swinging "kitchen doors" that close automatically. The doors can open only one way or both ways. There can be dual doors. You can require the player to use the door before it opens, or you can make a control panel for the door. You can do almost anything your imagination can conceive. You can try and make all the super complex sci-fi doors you ever imagined. You know, the ones that open for 10 minutes with a lot of hissing noise and moving parts. But for this tutorial, we'll settle for doing the most typical door we had in Max Payne. That's a regular door that opens when any character runs against it. It can open both ways and it stays open.

Let's start by adding animations to our door. Briefly, the animation system in MaxED works so that you define keyframes (different positions in the worldspace) for an object, and then define animations between the keyframes.

And since the door is going to be rotating, we are going to have to place its pivot point correctly. All the rotating objects always rotate around their pivot point, and if the pivot point is in the wrong place in our door, the door will move around from it's location while it's rotating. The location of the pivot point of the door of course depends on what kind of door we are making, but since we are making a door that opens both ways, we should place it as below. In real life, you do not see much doors that are hinged like this. Also if you make the door frame to match the door exactly like in here, the meshes are going to overlap when the door opens -- nobody will notice :-)

So move the grid center as it is in the picture, select the door, and press P to adopt the pivot from the grid.

Next we get to add the actual keyframes. Select the door, and press TAB to bring up the keyframe dialog. There's one existing keyframe there. Rename it as "closed". Then press Add. It asks for a name for the new keyframe, so let's name it as "opened1".

Next we need to define what is the position of the door in the "opened1"-keyframe. Double click on the name of the keyframe to make sure it's the current one, and close the dialog. Now we need to rotate the door, and there's few things to consider when rotating objects in MaxED. First of all, around which point in the the world to rotate the object? We can rotate objects around any of their vertices, or around their pivot point. Rarely used is the option to keep Shift pressed while rotating, which makes the object rotate around it's geometry center.  And we can select whether to rotate by world axis, or by the object's local axis. Or obviously we can make a temporary object anywhere in the world and rotate any other object around any of it's vertices. It's easier to just try out rotating things than to try to explain it. First take a look at the lower right corner of the rendering window in F5 mode. There it indicates the current rotation mode. In this picture it says VTX (vertex) and WLD (world), which means if you now rotate the door, it will rotate around the selected vertex and by the world axis. Try it around, you can rotate with keys X, Y and Z, or optionally with 1, 2, and 3. Might be a good idea to save before you mess around too much though.

 

You can toggle between vertex and pivot rotation with A, and between world and local rotation with W. Change the modes and try out rotating the door around again.

Got it? Ok, good, now as your door is "all over the place", it's a good idea to revert to that save you had before you started messing around with rotating things. Then make sure pivot rotation by world axis is selected (PVT WLD). Select the door, press down Y (or 2), and move the mouse sideways until the door is in its desired position.

TIP: You can change the angle snap from the Preferences. It's a setting called Kbd Tilt angle, but it affects to all rotations in MaxED, including polygon tilting. Also if you've got an exit on the doorway, the door might appear to partially disappear. This is nothing to worry about though, but if it cause problems, you can turn the exit acceleration off from the Preferences.

Now we have the first keyframe in position. Since we want to door to open both ways, we add another keyframe to the opposite direction. Open up the keyframe dialog (TAB), add a new keyframe called "opened2", close the dialog, and rotate the door 180 degrees around.

Got it? Good, then we can add the animations. Select the door, and press Shift+TAB. You get the animation dialog, in which you can add, delete and rename animations. You can change the keyframes and the length of the animations, and you can manipulate the animation graphs. You can also enter the message dialog from here.

Press Add-button to add an opening animation. Select "closed" as the Start keyframe and "opened1" as the End keyframe, and press OK. Now it might be a good idea to rename the animation as "open1", and alter it's length to, say, 1 second.

Also add the animation for opening to the other direction. Obviously otherwise do all the same, but select "opened2" as the End keyframe.

Now you can view the animations by double-clicking on the name of the animation. Also try manipulating the rotation graph to see what it does. It's actually pretty self-explanatory. Try pressing Ctrl+Q in the rotation graph dialog. Also you can change the sample rate of the animation by pressing Ctrl+numpad/  and Ctrl+numpad*. You can also copy and paste animation graphs with Ctrl+C and Ctrl+V

Next let's add triggers for the door. You should add triggers to the both sides of it. So align the grid on the door, and add a character collision trigger (N in F3 mode) on the middle of the door. Name it as T1, and change it's radius to 0.2m from its properties, and then lift it up from the door a bit. Repeat to the other side. It's also be a good idea to group the triggers to the door directly. (Select a trigger, press G, and click LMB to the door)

Then open the FSM message dialog of either one of the triggers (4 in F5 mode). When any character touches the trigger, we want it to animate the door. So, add message parent->do_animate(open1); to it's T_Activate tab. Make sure though that the animation indeed is "open1" that you want the door to execute. It's obviously "open1" for the other trigger and "open2" for the other.

Then open the FSM message dialog of the door, and there you will find tabs called Animation(open1) and Animation(open2). Add these messages to the both tabs:

T_Enable(false); messages are for disabling the triggers as the door opens

A_Play3DSound(dynamic, door_wood_medium_open, ""); is for playing a sound effect as the door opens.

These messages above are in a message list called Leaving 1st keyframe, which means the messages are sent at the start of the animation. There's also Reaching 2nd keyframe message list that sends messages at the end of the animation, but we won't need it now.

The rest of the messages are in a message list called Returing to 1st keyframe, which means these messages are sent if the given animation starts to play, but is ordered to return back to the beginning for some reason. So T_Enable(true); messages obviously enable the triggers if it happens that the door closes. And also a sound effect is played here. Now don't forget to copy these messages to the "open2" tab as well.

Still one more message to add, and it goes to DO_MovedToInvalidPosition tab. As you might remember, this is event is triggered if something blocks an animation. Let's add message this->DO_InvertAnimation(); there. This is the message that makes a currently playing animation to return to the first keyframe.

Now you can export the level and try it out! You should add a jumppoint to the other side of the door as well so that you can jump there in the game (with insert or delete) to make sure the door works from both ways. Also try typing DrawFSM to the console to see visualizations of the triggers and DO's, and to see if the triggers disable themselves as you intended them to. You can disable it with DrawFSM_Off.

Then we have just one problem left, we need to make sure there's no AI net passing through the door when it's opened. Otherwise enemies might run against the opened door like a bunch of suicidal lemmings. There's a special material category called ai_node_collision_nodraw, and if you texture an object with a material in this category, it means that AI nodes will collide against it. Without going into the details yet, you fix this problem by adding these little AI blocks to the positions where the door would be when it's open. Remember to do this to the both sides of the door.

Let's not get into calculating AI network yet, we'll get to that with the AI tutorial, but for now, let's settle for this.

TIP: Now that you've got a DO with keyframes with it, you might wonder how to move and rotate the whole object around, not just the selected keyframe. You can do that by pressing down Ctrl while moving objects. Otherwise all the moving and rotating commands are just the same. Although notice that you can't use Z or X for rotating objects while keeping Ctrl pressed, because these would equal to Undo and Cut. Even though there is no undo in MaxED, Windows takes over when pressing Ctrl+Z and it doesn't work. You should use Ctrl+1, Ctrl+2 and Ctrl+3 instead when rotating objects with keyframes.

Coder attitude with FSM's

You have probably already realized that there are countless of ways to make same things happen in Max Payne, and it might be that we did things here a bit differently as you would've liked to do them. For example, you might want to disable the door triggers in their own t_activate tabs. However if you do that, you still need to disable both triggers always when either one is touched. In general when doing FSM's and you aren't sure how you should do something, try to do scripts so that messages that do the same thing aren't sent from multiple different places or objects. For example the door we did, as the animation itself disables the triggers, it is easy to add more triggers all over the place. All you need to add is that you send DO_Animate(); message to the door from the new trigger, and disable the trigger via the door's animation (and enable it back if the animation returns to the 1st keyframe). This way you can keep things structured logically and in the same place even if you do additions to your FSM's.


The door we did is not the best example to describe the importance of the structure in FSM scripts, but above is a door that is controlled by five triggers, and two ways of enabling and disabling the triggers. As you can see, you can make simple things surprisingly complicated if the structure is wrong. Because of the flexibility of Max Payne's FSM scripting, it is perfectly possible to create spaghetti and get yourself into troubles if you don't pay attention to how you do things. This is especially true when we get to advanced AI-scripting. Putting some time into thinking about this is definitely worth it. If you just fire away and send messages from whatever object first comes to mind, you will soon find yourself typing in a lot more messages than you should, and eventually you will spend 10 times more time fixing bugs than it would've taken to structure it properly.

Talk about fixing bugs, there's some methods of finding them from your FSM scripts. You can try this with the level you've done so far. One aid is taking an FSM dump of the level. You do this in movement mode (space) from Mode commands > Dump FSMs as XML. You can seek for errors from there. Also in the game, you can type messagefilter_levelonly to the console to get a continuous dump of the messages moving in the level to the console. You can get a filedump of the console with x_consolemode->cm_dumptofile( my_dump1.txt );. And don't forget Draw_FSM that was mentioned above.

Also something you will be using a lot for testing and debugging your dynamic content is Partial export. You do this simply by selecting a room or multiple rooms (by pressing down Shift) in F5 mode, and then selecting Export selection from the Mode commands dialog. Notice however that the selection must contain a jumppoint.

Exit disabling

Exit optimization is a powerful optimizer already on it's own. In some cases you might want to help it even more to gain some extra speed by manually opening and closing exits. The most obvious use for disabling an exit is when there's a door that is closed on top of the exit, especially if the door happens to be a swinging door that closes automatically.

Let's add exit disabling functionality for our door. First make sure the exit is totally enclosed by the door at it's "closed" keyframe. If it isn't, either delete the exit and create a new one to the correct position, or move the door so that it encloses the exit. Then enter the door's keyframe dialog (TAB) and double-click on either one if it's "opened" keyframes (we do this just so that we can actually see the exit).

Now make sure that "Select culled polygons" is not enabled in the Preferences. If it was enabled, it would make it harder to do what we'll do next. Enter F4 mode, point to the exit and press Enter. This will bring up the exit renaming dialog. Name it as toggle1. Now go to the other side of the exit, point to the exit, press Enter, and rename it with the same name. That's right, there's not actually just one exit there, but two! The reason we can name them with the same names is that they belong to the different rooms. And now as there's two exits, it is possible to create doorways that are disabled only from the other side. The reason we disabled "Select culled polygons" was that if we had it enabled, MaxED could select either one of the exits regardless of in which side we were ourselves.

Let's still rename the rooms in this level so that we know what they are called. The other one is probably already called Startroom (if it isn't, rename it), and let's rename the other one as room2. Now open up the FSM message dialog of the door, and to its Startup tab add messages:

::startroom::toggle1->EnableExit(false);
::room2::toggle1->EnableExit(false);

This will close the exit at the startup. Then we need to make sure it gets enabled as the door opens, so to both of the animation tabs add

::startroom::toggle1->EnableExit(true);
::room2::toggle1->EnableExit(true);

Obviously put these into the Leaving first keyframe message list. Then still just to make it perfect, let's disable the exits if the door closes because of something. Just copy the messages from the Startup tab to the Returning to 1st keyframe message list.

Now just export the level and try it out. You can make sure the exit is disabled when the door is closed by typing maxpayne_gamemode->gm_drawoutlines(1); to the console. You can see all the outlines that the engine draws through walls, and if you did things correctly, you can see it doesn't draw any outlines through the door before it's opened.

FSM states and custom strings

Let's use an existing object for clarifying the use of FSM states and custom strings. Open up the example level of this tutorial, which you hopefully downloaded before you got started. If not, download it from here. You will find a breakable crate from the level. As you can see, the crate breaks in 5 different pieces. 4 sides and the top. What we had to achieve here was that the top piece falls down if any of the two other pieces were destroyed. Opening up the FSM dialog of the top piece, you can see it has some states and custom strings.

If you open up the message dialog of any of the side pieces, you can see that when they break, they send FSM_Send( add ); to the top piece. Opening up the message dialog of the top piece again, you'll find a tab there called FSM_Send(Add). Yes, there's a connection between there. This is a custom event for the object that is triggered when it receives FSM_Send(Add); from somewhere.

As you can see from the FSM dialog of the object, it's in state called 1 by default. In it's FSM_Send(Add) tab you can see that if it triggers the event while in state 1, it will send a message FSM_Switch(2);

This means it will change its state to 2. In other words, this happens when the first one of the side pieces is broken. Then let's see what would happen if the top piece would receive FSM_Send(Add); now again, when another second side piece breaks. Select "2" from the "state-specific" drop-down list of the FSM_Send(add) tab.


As you can see, when the second piece breaks, the top piece receives FSM_Send(Add); for the second time, and it will animate itself to drop down. It will also change its state to "disabled" so that none of the "Add" custom strings it will be receiving from now on has any effect.

Custom strings are especially useful for structuring things properly. You can keep the messages where they'd logically belong. The camera prefab (look below) is a good example of this, there is a number of messages to send when you startup a cinematic. Now you can either send them from whatever triggers the cinematic (there can be multiple events that do that), or then you can just send one custom string to the camera from wherever and you'll know where the messages are when you need them. If you have multiple events that might trigger the camera, you can easily use "enabled" and "disabled" states for the camera, so that it switches itself to "disabled" when the cinematic starts, and all the custom strings it might receive while in the cinematic, it will ignore. And/or you can of course disable all the triggers from the camera's "start" custom string tab.

Exploiting prefabs

Prefabs are some commonly used objects that are prefabricated and ready to be put into the levels. This obviously saves a lot of time and nerves of the mapper as he doesn't have to make every single door from scratch, for example. For the end of this tutorial, let's cover some preferred ways of using prefabs. As already mentioned, There is no special prefab-system or UI in MaxED. But if you make your dynamic objects cleverly and prepare them properly, you can reach something that  can be described as a prefabs system.

How to make one? Open up the level where you made your door again. After you have tested your door and you are sure it's 100% working and all, let's make a prefab out of it. First we should create a handy parent for the whole machine, and group everything to it. Create an object next to the door with dummy material (If you don't have one yet, add dummy material category and import some texture in there). Then set its pivot point to some convenient corner that you want to use as a reference point when copying the object. Note that you can't see the pivot points of the static objects, but that doesn't mean they don't have one. Yu can set it just like you would for a dynamic object; move the grid's pivot to the desired position, select the object and press P.

TIP: If you have to group objects that are in different rooms together, you can do it by first selecting the desired children, then moving to the room with the parent, entering F5 mode, and selecting Mode commands > Grouping > Group selected. In other words; don't use the keyboard shortcut.

Next select the door parent dummy in F5 mode, and copy  it to the clipboard (Ctrl+C). Now you can either make a copy of the door by pasting in F5 mode, when the new door appears to the same position as the original and you have to move it a new position with arrow keys, or you can paste it in the F3 mode, when it will get pasted to the grid tick by the reference point. Try it out. You can delete objects with their children in F5 mode by Ctrl+Delete; a handy feature once you've got dozens of doors around the room.

To create yourself a library of prefabs, import all the materials that you need to separate empty document (insert them from your current file) paste the object (in this case, the door) into that somewhere around the world origo. Make sure, that in that level there just the door object with all the necessary stuff, but no room or something other extra around it. Finally it is wise to purge all unused materials to make sure that the prefab has just the textures it needs.

Then, as you need a good working door when you are working with some other level, just use  File > Insert document... to get it in. This inserts the door and those materials that are not yet there. If the your door prefab was near the world origo, you should be able to locate it pretty easily (Use F12/C to reset the grid and the camera) after insertion. Then, just cut it and paste around as usual (F3/Paste to is good for this). If you do lot of different general objects and put them into some folder with descriptive names, you have something that can be described as a library of prefabs. 

With a door though there's typically one thing that causes problems when it's prefabricated; it's usually placed on top of an exit. The game will complain if you've got static objects going through exits, or static objects that are grouped to an object in some other room (in this case the door parent might go through an exit, and the other one of the AI net block definitely will be in a wrong room for its grouping). It is perfectly fine for dynamic objects to go through exits, but when there's static objects involved, you might have to do some regrouping after you've pasted the prefab to its location. Also if you need to rotate the prefab, don't forget to rotate all of it's keyframes (press down Ctrl while rotating).

Prefabs from Remedy

Here is also some prefabs that were used in Max Payne, you can take a look at them and try to figure out how they work.

Cameras - Two cameras that work by custom strings. It's easy to copy more cameras for your cinematic. The animation in the camera defines the length of the cut for each camera, and obviously you can just add keyframes to the camera to move it. Or you can make a "camera_mover" dummy to which the camera is grouped to.

Ceiling fans - Three breakable ceiling fans

Crates - A set of breakable crates.

Exploding barrels - Four different kind of exploding barrels.

Fire extinguisher - Breaks when the valve is hit.

Gas bottles - Various gas bottles. Break when the valve is hit. Some fly out, some rotate, some just explode.

Gasoline canister - A simple gasoline canister that explodes.

Small breakables - Some bottles, a drinking glass and a porcelain plate.

Soda machine - A vending machine.

Sodacans - Four cans of soda. Two of these fly in the air when hit.

Swing door - Kitchen doors that close automatically.

TV's - Four similar TV's with just different colors.

Valkyr containers - Breakable Valkyr containers. The one that is standing tips over when it breaks.

Wooden door - The door we did in this tutorial as a prefab.

--- --- ---

Chopper - Due to numerous requests by modding community, the police helicopter prefab used in the game is now available. You should be warned that this is one of the most complex dynamic objects used in Max Payne. Understanding the operation requires fluent animating and FSM scripting skills.