Back to Mechstorm.

Mechstorm's Complete Map Making Tutorial or MCM for Short. Covering Mechwarrior4, Mechwarrior4:BK and Mechwarrior4:Mercs. If you have any questions feel free to drop by our Mechstorm Editor/Scripting help Forum after you have registered on our forums.

Back to MCM Menu

Mission Making:

Introduction:

So you want to make your own missions....

Mission making has to be one of the most fun and exciting things you can do with the Mechwarrior 4 (all versions) editor. Whilst the original Editor for MW4 had some limits in this department it did have the best documentation. The editors for Black Knight and Mercs are more or less the same but lack certain important documents. In fact the BK editor is probably the least buggy of all the editors available for various versions of Mechwarrior4 and its addon packs.

So you really should download and install the Mechwarrior 4 original editor if only to get the documentation. Then copy the orders doc across to your Mercs install because the devs made a complete hash out of the Mercs orders doc. The Mercs Editor was a rush job from start to finish. Now we have to live with the problems the Mercs Editor has created.

Its a fact that very few bugs that where reported got fixed after BK was released, so the newer the editor, the more bugs it had. BKs editor is only the best because the Devs did fix the initial problems with the MW4 editor. After that they stopped listening.

All this is important because making missions for mechs is 50% knowing where the bugs are and working around them. Spending some time in the Mechstorm forums looking at the bugs people post is definitely a good idea. It will stop you from becoming frustrated later.

In this tutorial we will assume you know how to make maps already. If you do not then my advice is learn the map making process before you even attempt to make a mission. Its too much to learn when you have to figure everything out at once.

Mission making is 20% editor work, 80% scripting yet requires you to at least know basic map making skills and how to setup an nfm for multiplayer. So this will be mostly about scripting with some editor work. Along the way I will explain what works in Multiplayer and what is single player only. As a rough guide just ask your self, is this feature cool. If the answer is yes then its single player only. Multiplayer doesn't support half the stuff single player supports but the mission scripting is almost the same.

Covering the Basics before we start.

Static Vars.

Its time to show you the commands we will be using to make our mission and explain how they are used. Its important you master these but do not worry if you do not understand them after reading this section. This is just an introduction to explain the basics. A better description will appear later on when we are ready to show you some examples. Now open up the Mechwarrior Mercenaries\EditorDocs\ABL_Orders.rtf.

Important sections you will be referring to a lot in your own work are.

Variable types
Prefixes
Group/Team Functions
Timer Functions

So lets look at these first.

Variable types are used at the top if your script to initialized or Setup the variables you will be using. Now the list is as follows. The word after the var type below is usually one the map maker has invented. They are not commands and you will not find them listed in the orders doc. EG static Integer gametimer = static and Integer are key words defined by the game but gametimer is one I made up. The word was chosen because it describes what I intend to use it for.

Integer a basic Integer, whole numbers
real a basic floating point number
Boolean a Boolean that can be true or false
char a single character
ObjectID a reference to an object in the world
LocPoint a location in the world (3d point)
TimerID a reference to a timer in the mission 0-31 only
CameraID a reference to a camera
PlayerID a reference to a player

Boolean, Integer, ObjectID, Locpoint are all variable types you will use a lot.

TimerIDs are a little useless because you can initialized a timer like this using an integer instead. EG...

static Integer gametimer;

If you used the Timerid instead of Integer it would look like this.

Static TimerID gametimer;

So don't worry about that one.

Booleans are True or False vars, which means a Boolean can only ever mean True or False, it cannot ever be anything else. These are the first things you will blame when your bot fails to follow a path. But trust me, its not this, its just the way mercs bot movement works. I'll cover this later.

You initialize Booleans like this.

Static Boolean myvar;

Interger's are any whole numbers, e.g. 1,2,3,4,5,659 etc.. etc.. and are initialized like this.

Static Integer gametimer;

ObjectIDs are very interesting, they are any in game object, vehicle, a mech, a nav point. All are Objects and require an ObjectID

You initialized them like this..

Static ObjectID mybigtank;

Now objectids are different in another way, if you want a var to mean eve_mytank so you don't always have to use the phrase eve_mytank but instead want to just call it mytank then you must use an Static ObjectID declaration to do it. Later I will present you with 2 scripts showing the 2 different ways to use these. Yet if you just put a command anywhere in the script to check eve_mytank rather than mytank then you could skip the declaration of eve_mytank completely. It means less typing but you miss the chance to change the vehicle mytank refers too because eve_mytank is the actual name of real tank on your map where as mytank is just a var that can be set to any tank on your map and any time in your script.

LocPoint is used to get a set of 3D coordinates from the game so you can say this tank is HERE and be sure it really is there. Its not as hard to use as it sounds because you can use the getlocation command to get the 3D coordinates of an object most of the time. You don't need to bother knowing them your self though it is always an option if need to set them manually.

A locpoint is initialized like this.

Static LocPoint mylocation;

All of these declarations are done before the function init part of the script under the Var command all scripts have at the top of them. You may notice that functions also have a Var section so you can initialized any vars that function uses within a function too. Which is good if you reuse functions a lot and want to just cut copy and paste them into other scripts. It can save you a lot of work.

Functions.

All scripts have a function init, this is used to setup the vars values. So if you declared a var as a static ObjectID mybigtank at the start of a script, then in the function ini you'd actually tell it what mybigtank means.

E.g.

mybigtank = eve_mytank;

Where eve_ tells the game this is vehicle or mech. All game objects use a prefix similar to this when called inside a script. The editor doesnt use these but its assumed the prefix is added to what ever editor name you give an object like a building, turret or mech. Also a space in a name in the editor is translated into an underslash in a script eg in the editor you may call your tank my big tank but in a script it would be my_big_tank. A tip worth remembering :)

A Integer might be set like this. Though it can be set to any number.

mycounter = 0;

Locpoints are rarely setup here because you wouldn't know the location usually until the game has started so do not worry how locpoints fit into a function. You will know when you need to declare a locpoint location here because you will be trying to name an area for a specific reason and not getting an area of the map as most people do. Confused ? don't worry, this is just idle chatter. Not important right now.

You can have other functions in your script below the function init. A function looks like this.

Function myfunction;

var
Integer I;

code

endfunction;

The above Function would work in any script but has no commands in it so nothing would happen. If you need to make a new Function then everything you see above should be cut and pasted into your script below the Function init and the myfunction name should be changed to something suitable. The last line of all functions is always endfunction. So paste that below the endfunction for the function init.

Then add the commands you want to use to the Function.

When you want to actually "Call the Function" inside your script at various points you just type the Function name like this.

myfunction;

The script will then run the Function every time it sees that line. Easy stuff really. It could be as simple as settting a nav point to for the player or getting a locpoint you check frequently. Functions tend to sets of commands you use a lot but only want to type once.

Common Commands.

Time to take a closer look at some common commands we will be using a lot in your own work. You should refer to the orders doc for their meaning. Here is a list of commands I recommend you get to know well.

GetLocation
SetTargetDesirability
SetNavpoint
SetCombatLeash
OrderDie
OrderMoveTo (all of the OrderMove commands)
Destroy
IsShot
IsWithin
IsDead
GroupAddObject
GroupAllDead
Settimer
TimeLesser
TimeGreater
StartTimer
ResetTimer (good one this)
RevealObjective (requires you setup the objective text in the editor first)
HideObjective
FailObjective
EndMission
RevealNavpoint (very important for missions)

Bot movement, paths and patrols.

It is vital you understand the limits of your maps lattice, this effects how well your AI vehicles and Mechs will move around in your mission. Even on a perfect map the lattice system employed by Mercs is far from perfect. You will notice bots stood in an open desert waiting for the bot in front to move before it moves yet it will have room at both sides to walk past.

Why it doesn't may be because somebody dropped a small leaf on one of the bot lattices nodes and your 100 ton mech cannot get past it. Bot movement is really frustrating and should never be considered easy. You may learn the commands of by heart but placement and an general understanding that its going to be a pain in the ass is required when ever you use paths, or movement commands. Its always trail and error. Never automatically assume your commands are wrong just because a bot wont move. Half the time its maps bot lattice causing problems.

So Rule 1 is Movement is frustrating, always double check it works, never assume anything and always check 5 or 6 times. Not 1 or 2 times, but 5 or 6 times. I mean it. If you ignore this point you might as well pack up and forget about making missions.

The following commands should be tested heavily if used.
OrderMoveTo
OrderMoveToRigid (especially this one, your path is vitally important to this command)
OrderMoveResumePatrol
OrderMoveResumePatrolRigid (notice the Rigid part, that means it will never leave the path. Which is bad is there's a building in the way).
OrderMoveFollow

The most forgiving movement commands are these.
OrderMoveToObject
OrderMoveFollow

These allow the bots to decide which way they want to go to follow or go to an object. When using these commands the bots may appear to be walking away from where they need to go. But this is just an attempt an navigating around something that's blocking their movement. You may not see what it is, but the bots do. We will be using one of these in this tutorial later.

Making the mission.

Missions are a combination of bot scripts and a master control script. The script that controls the mission is usually named after your map and found in your missions script folder. E.g. mymap.abl and this is what I called your master mission script. Its usually best to write this script later. Once all the bots are placed and tested. Mission objectives are controled from the master mission script usually so these too are usually set up last.

Bot scripts are placed in your missions script folder and assigned to your mechs by clicking on the mech and bringing up its properties. Here is a pic of a bots properties. Please notice the alignment says player, it should be set to enemy. This is one of the differences between multiplayer and single player missions. For multiplayer the alignment should be a team.

As you can see this mech is a cougar, its on the players side and uses the mechai as a driver, its script is named under script. The script it self is one I copied to my missions script folder before I opened the editor but you can do this before you open the properties of a mech and it will still appear as available.

As you can see its based on my leaderbot script so lets look at the real leaderbot script and modify it for our mission. Leaderbot scripts mean its a Yogi bear bot. Smarter than your average bot lol :)

Here is the unmodified leaderbot script. You can download it from the mechstorm.net download area. Its part of the lancemate bot scripts.

// Mechstorms Leader Bot by Giskard.
// Requirements: None.
// Purpose: Designed to work with the lancemate script so their leader behaves in an intelligent manner.
//
// Don't make kill this bot your self, get one of your mates to do it. Somebody with no scripting experience :)
// Then sit back and watch what this bots lance mates do to him when he destroys their leader.
// Good for laugh :)

fsm leaderbot : Integer;


const
#include_ <content\ABLScripts\mwconst.abi>


type
#include_ <content\ABLScripts\mwtype.abi>

var
static Integer attackRange; // At what range do I start shooting?
static Integer withdrawRange; // At what range do I withdraw?
//static ObjectID mechlance;
static ObjectID shooter;
static Integer hit;
static ObjectID revenge;

Function Init;
code

//mechlance = groupobjectid(3);
hit = 0;

// script-specific variables
attackRange = 9999;
withdrawRange = 9999;

// driver settings
SetFiringDelay (ME,0.5,1.0);
SetIgnoreFriendlyFire (ME,true);
SetIsShotRadius (ME,200);
SetEntropyMood (ME,AGRESSIVE_END);
SetCurMood (ME,AGRESSIVE_END);
SetSkillLevel (ME,100,70,100);
SetAttackThrottle (ME,100);
SetMinSpeed (ME,100);

// EnablePerWeaponRayCasting (ME,TRUE);

endfunction;

state StartState;

code
trans WaitToAmbushState;

endstate;


state WaitToAmbushState;
code

if (Bot_FindEnemy(attackrange)) then
trans AttackState;
endif;

OrderMoveLookOut;
endstate;

state AttackState;
code
if (LeaveAttackState(withdrawRange)) then
hit = 0;
trans WaitToAmbushState;
endif;

// check to see if im getting roasted.
If ((isshot(me)) == true) then
hit = hit + 1;
shooter = Whoshot(me);
SetTarget (me,shooter);
endif;

// dam need to change my tactics
If (hit == 5) then
hit = 0;
trans attackstate2;
endif;

OrderAttackTactic(TACTIC_PICK_BEST,TRUE);

endstate;

state AttackState2;
code
If (LeaveAttackState(withdrawRange)) then
hit = 0;
trans WaitToAmbushState;
endif;

If ((isshot(me)) == true) then
hit = hit + 1;
shooter = Whoshot(me);
SetTarget (me,shooter);
endif;

// walking away to sniper range, then switching tactics If IM still being hit.
If (hit == 5) then
hit = 0;
trans attackstate3;
endif;

OrderAttackTactic(TACTIC_BACK_UP_AND_FIRE,TRUE);

endstate;

state AttackState3;
code
If (LeaveAttackState(withdrawRange)) then
hit = 0;
trans WaitToAmbushState;
endif;

OrderAttackTactic(TACTIC_SNIPE,TRUE);

endstate;

state DeadState;
code
revenge = WhoDestroyed(me);
SetTargetDesirability(revenge, 100);
orderDie;

endstate;


endfsm.

Now we are going to setup an objective for this mech and modify this script so the bot carries out its objective whilst giving the player a chance to defend his objective. We could make the bot go straight for its objective and ignore everything else. However, if we did that the player wouldnt stand a chance of stopping it. So we have to allow room for the bot to be distracted by the players actions.

Follow these instructions carefully or you may regret it later when we add more to the mission.

Lets make a target for this bot to destroy.

Place a building on your map and add this script to it. You can assign it a script by clicking on the building and getting its properties up. Heres an example. Just make your buildings properties look like this but instead of using the outpost script which you dont have, select your script (the one below the picture I have supplied for you). If your making this for multiplayer, you would usually assign this building to a team instead of making its alignment Player.

Now heres the script you will need.

fsm targetbuilding : integer;

Constant Definitions

const
#include_ <content\ABLScripts\mwconst.abi>

type
#include_ <content\ABLScripts\mwtype.abi>

var


function init;
code
endfunction;


state startState;
code

GroupAddObject(GroupObjectid(5),me);

SetTargetDesirability(me,20);
endstate;

state deadState;
code

orderdie;

endstate;

endfsm.


This script is empty except for 3 commands. That is the SetTargetDesirability(who, desirability), Orderdie and GroupAddObject(GroupObjectid(5),me) commands.

What we have done is make this building a more juicy target for the bot by using the SetTargetDesirability command. We also added it to group 5 so we can check if its dead later from the master mission script. If we didnt add it to group we would have to use the isdead command instead from the master mission script. Which is also good but if we have several buildings we want the bot to destroy and gave all those buildings the script above but without the groupaddobject command, we would have to use isdead on all the buildings to see if they were all dead. This way we need to do only one check to see if 1 or even 100 buildings have been destroyed. We only need the one script for all the buildings too. So its much easier all round. I will give you a good example of the advantages of this when we expend the basic mission. You will see for your self how much easier it is this way.

Once you have added the script to a building the bot will automatically prefer to target it first. So you do not need to tell it to shoot the building, it will. It also gives the player a chance to distract the bot. So there are no garrentees the bot will become a fanatic intent on going solely for the building. The player may get its attention first. Which is good practice in mission designing. Setting SetTargetDesirability higher than 20 will make the bot more of a fanatic, setting it lower will reduce its desire to utterly crush the building.

Now we need to modify the bot above to take out this target. Since we need to move the bot to the objective and since the movement bit is tricky. Lets keep it simple and use a navpoint. Please an navpoint called objective1 on the map near the building you placed

Here is the modified script.

First we change the fsm name, I changed the leaderbot to myleaderbot and saved the new script as myleaderbot.abl. I'll explain the rest below.

fsm myleaderbot : integer;


const
#include_ <content\ABLScripts\mwconst.abi>


type
#include_ <content\ABLScripts\mwtype.abi>

var
static integer attackRange; // At what range do I start shooting?
static integer withdrawRange; // At what range do I withdraw?
static ObjectID mechlance;
static ObjectID shooter;
static integer hit;
static ObjectID revenge;

function Init;
code

mechlance = groupobjectid(3);
hit = 0;

// script-specific variables
attackRange = 9999;
withdrawRange = 9999;

// driver settings
SetFiringDelay (ME,0.5,1.0);
SetIgnoreFriendlyFire (ME,true);
SetIsShotRadius (ME,200);
SetEntropyMood (ME,AGRESSIVE_END);
SetCurMood (ME,AGRESSIVE_END);
SetSkillLevel (ME,100,70,100);
SetAttackThrottle (ME,100);
SetMinSpeed (ME,100);

// EnablePerWeaponRayCasting (ME,TRUE);

endfunction;

state StartState;

code
GroupAddObject(mechlance,me);

trans movetoobjective;

endstate;

state movetoobjective;

code
OrderMoveToObject (ena_objective1, 600);
if (IsWithin(ena_objective1, me, 600) then
ClearMoveOrder(me);
trans WaitToAmbushState;
endif;
endstate;


state WaitToAmbushState;
code

if (Bot_FindEnemy(attackrange)) then
trans AttackState;
endif;

OrderMoveLookOut;
endstate;

state AttackState;
code
if (LeaveAttackState(withdrawRange)) then
hit = 0;
trans WaitToAmbushState;
endif;

// check to see if im getting roasted.
if ((isshot(me)) == true) then
hit = hit + 1;
shooter = Whoshot(me);
SetTarget (me,shooter);
endif;

// dam need to change my tactics
if (hit == 5) then
hit = 0;
trans attackstate2;
endif;

OrderAttackTactic(TACTIC_PICK_BEST,TRUE);

endstate;

state AttackState2;
code
if (LeaveAttackState(withdrawRange)) then
hit = 0;
trans WaitToAmbushState;
endif;

if ((isshot(me)) == true) then
hit = hit + 1;
shooter = Whoshot(me);
SetTarget (me,shooter);
endif;

// walking away to sniper range, then switching tactics if im still being hit.
if (hit == 5) then
hit = 0;
trans attackstate3;
endif;

OrderAttackTactic(TACTIC_BACK_UP_AND_FIRE,TRUE);

endstate;

state AttackState3;
code
if (LeaveAttackState(withdrawRange)) then
hit = 0;
trans WaitToAmbushState;
endif;

OrderAttackTactic(TACTIC_SNIPE,TRUE);

endstate;

state DeadState;
code
revenge = WhoDestroyed(me);
SetTargetDesirability(revenge, 100);
orderDie;

endstate;


endfsm.

Now ive added a move state between the start state and the state that allows the bot to start looking for targets to shoot. Here is the new stuff on its own.

state StartState;

code

GroupAddObject(mechlance,me);
trans movetoobjective;

endstate;

state movetoobjective;

code
OrderMoveToObject (ena_objective1, 600);
if (IsWithin(ena_objective1, me, 600) then
ClearMoveOrder(me);
trans WaitToAmbushState;
endif;
endstate;

Theres something else thats changed in this that ive not mentioned. Remember the GroupAddObject command I explained earlier, well notice this one has the name mechlance in it. Go back and look at our bot script again and see if you can spot what group mechlance refers too. Notice its not group 5 and it has been initialised and then had its true value set as described in the first part of this tutorial. This is the other way to setup groups using a var. Using a var in this way means you can change the vars value by editing one line but if you used the groupobjectid directly youd have to edit all lines that used the groupobjectid rather than the mechlance var we used. Just something for you to check out later. Its all in the bot script and you have an example of the groupobjectid use in the building script too. Compare the two and learn how they differ, both ways work but sometimes one way is better than another if only to save typing in long scripts with lots of groups in them. EG the longer method becomes the shorter method complex scripts. You will also see how one is easier to use when your making many similar scripts but want to assign the bots to different groups vs one script and one big group. We are basically using one big group here so it does not matter. Both ways work the same at the end of the day.

Study and learn, reading scripts is a skill you have to master and since theres nothing wrong with the above 2 scripts and they both show 2 different ways to do the same thing, they make fine examples for you to learn from. You will learn a lot by looking at both scripts and seeing what extra work is needed to get the mechlance var to work. Mechlance is a word I made up btw. its not a command. You could have called it mybigmetalmonster. It would not make any difference.

Anyway, these changes allow the the bot to move directly to navpoint objective1 but when it gets within 600 meters it stops moving to the nav point and starts looking for targets. Since 600m is well within its scanning range it should spot the building and start to go for it.

What this script does not do allow the bot to return fire if it is shot at whilst moving towards the building in the first place. It can only do that when it gets within 600m. The advantage is the bot will never get distracted until its within the area where the missions battles will take place. Keeping control of your bots is important. This is a simple example of how to do it. There are more complex and problem prone ways to allow the bot to shoot and move to its destination. See the OrderMove commands in the orders doc for some examples.

Now you may notice the line ena_objective1 above. ENA_ tells the game this is a navpoint. You will recognise name as the name of the navpoint you added in the editor. Clearmoveorder tells it to stop moving to the navpoint and carry out the next order which is a IF question. It asks, am I within 600m of the navpoint.

If it is it switches to the WaitToAmbushState; and the bot basically becomes a true leaderbot. Its set loose and our control over it is relaxed. We let the bot do its thing so to speak. EG shoot stuff :)

Now in the editor after you have given the building and bot their new scripts so go to the tools menu and run check script. Any errors will be reported and need fixing. I'll let you learn how to bug fix your scripts your self. Remember we can help you in the Mechstorm forum if you ask. Its a skill you must learn.

To help you, try doing a search on google for a text editor called Context. It tells you what line number your cursors on at the bottom of the program. Which allows you to jump straight to error, no messing around. All my scripting for any game is done in this program. Its free and easy to use.

If you saved the mission and loaded up mercs now, you should see the bot run towards the navpoint and when it gets within 600m it will start shooting.

If the bot simply stops and does not do anything else remember rule 1: Movement is frustrating. Try using different movement commands to get around the problem your bot is having on your map or move the navpoint. This is why testing bot movement commands is a must.

The mission will work as intended but will not tell you if you have won or lost. For we need to move on to the Master Mission script and set up the objectives. This is how we control the flow of the mission and check whats happened and to who its happened. We will need to remember the groupobjectids assigned to both the Bot and the building. Since we will be using these to determine who won.

Master Mission Script.

Go to your missions script folder and open up the script that is named after your mission. Then cut and paste the script below into your missions script replacing all the text you see in it with this script. Then save it as the missions script.

fsm GENERIC : integer;


const

#include_ <content\ABLScripts\mwconst.abi>

type

#include_ <content\ABLScripts\mwtype.abi>


var

eternal integer playermech;

function init;
var
integer i;

code

playermech = getplayervehicle(epl_player0);

GroupAddObject(GroupObjectId(1),playermech);



endfunction;


state startState;

code

RevealObjective(eob_defendbuilding);
revealnavpoint(ena_objective1);
Setnavpoint(ena_objective1);

trans sit;

endstate;

state sit;

code

if (Groupalldead(Groupobjectid(5))) then
FailObjective(eob_defendbuilding);
trans endmissionfailed;
endif;

if (Groupalldead(Groupobjectid(3))) then
SuccessObjective(eob_defendbuilding);
trans endmissionwin;
endif;

endstate;


state endmissionwin;
code

EndMission(true, 15);

endstate;

state endmissionfailed;
code

EndMission(False, 15);

endstate;

state deadState;
code
endstate;


endfsm.

Now look at the start state above, we have revealed the Navpoint and set it as the players current navpoint using 2 commands. We have also revealed an objective called eob_defendbuilding, EOB_ tells the game this is an objective so the actual name is defendbuilding. We need to add this to the editor so go to editors options menu and select objectives.

Click on add then select the objective that appears and open it.

It should look like this but lack the text for the objective because we have not entered it yet.

Now change the Objective name to defendbuilding.

Ignore the Completion level and go directly to the Incomplete box. We want the player to defend our building so put something in there that tells the player to do just that.

Now go to the success box and give the player a well done message, he has won.

Now go to the failed box and give the player a failed mission message.

Hit ok and save your mission.

Now run the script checker again from the tools menu to be sure everything is fine. Fix any bugs you find. Again, bug fixing is part of scripting so you must learn it.

What you have just done is setup the objectives that the following lines from the above script check for.

if (Groupalldead(Groupobjectid(5))) then
FailObjective(eob_defendbuilding);
trans endmissionfailed;
endif;

if (Groupalldead(Groupobjectid(3))) then
SuccessObjective(eob_defendbuilding);
trans endmissionwin;
endif;

Notice we use the Groupalldead command to check if the building or the bots are dead and set the objective to failed or success according to the result of the IF question.

Another way to do this would have been to use the isdead command and entering the building name with its prefix.

Now load the mission and test it, the objective should appear at the start of the game. Once the bots are dead you should get a win message. If the building is destroyed you should get a lose message. Assuming everything went ok lets explain what we just did to make it work.

First of all we didnt use any vars but instead called the in game objects by name directlym so we didnt need any static objectid commands this time. Though its better if we use them. As this is a simple mission tutorial I will skip that part for now. What we used to set and reveal both the objectives and set the nav on the players hub was this part.

RevealObjective(eob_defendbuilding);
revealnavpoint(ena_objective1);
Setnavpoint(ena_objective1);

We then trans (told the script to move) to the sit state below.

state sit;

code

if (Groupalldead(Groupobjectid(5))) then
FailObjective(eob_defendbuilding);
trans endmissionfailed;
endif;

if (Groupalldead(Groupobjectid(3))) then
SuccessObjective(eob_defendbuilding);
trans endmissionwin;
endif;

endstate;

The game remained in this state, running it 60 times per second until either the bots where dead or the building was destroyed. It then jumped to the endmissionwin or endmissionfailed states depending on the outcome. The command Endmission then ends the mission after 15 seconds for us. The True or False tells it if it was a success or not.

state endmissionwin;
code

EndMission(true, 15);

endstate;

state endmissionfailed;
code

EndMission(False, 15);

endstate;

Now look up all the commands in the above script and learn what they do. You have a working example in front of you to use as a guide. The mission is now complete and you can play it as much as you want.

Unfortunately, its not the end of the tutorial. Its just the end of the basic mission. We will expand on the basic mission using some more commands and make it more interesting. But if you check how long this part has been and how much more there is to go you may start to realise why using Groups is a good thing. Especially when you see most of the work your going to do next because of the group commands is mainly cut and paste stuff. Dead easy. Just one more script to make first.

Expanding the basic mission.

Open up the leader bot script, the original one. You didnt just edit the old one without changing its name and fsm did you ? If yes then tut tut. I will not waste time explaining how to fix it if your not following my instructions. Go back and follow the tutorial properly. Theres no point moving on untill you have followed my instructions so far.

We want to give the player some team mates to help him defend the buildings. So rename the leaderbot script to playersmates and change the fsm line to playersmates too. Then save it off but keep it in your text editor.

Here is a script that uses the setcombatleash command to keep the players Bots near their objective. You can copy and paste this over your own playersmates script if you like to save time. I'll explain what we did to the leaderbot script below.

fsm playersmates : integer;


const
#include_ <content\ABLScripts\mwconst.abi>


type
#include_ <content\ABLScripts\mwtype.abi>

var
static integer attackRange; // At what range do I start shooting?
static integer withdrawRange; // At what range do I withdraw?
//static ObjectID mechlance;
static ObjectID shooter;
static integer hit;
static ObjectID revenge;
static locpoint defendarea;

function Init;
code

//mechlance = groupobjectid(3);
hit = 0;

// script-specific variables
attackRange = 9999;
withdrawRange = 9999;

// driver settings
SetFiringDelay (ME,0.5,1.0);
SetIgnoreFriendlyFire (ME,true);
SetIsShotRadius (ME,200);
SetEntropyMood (ME,AGRESSIVE_END);
SetCurMood (ME,AGRESSIVE_END);
SetSkillLevel (ME,100,70,100);
SetAttackThrottle (ME,100);
SetMinSpeed (ME,100);

// EnablePerWeaponRayCasting (ME,TRUE);


endfunction;

state StartState;

code
GetLocation(ena_ena_objective1,defendarea);
SetcombatLeash(me,defendarea,600);
trans WaitToAmbushState;

endstate;


state WaitToAmbushState;
code

if (Bot_FindEnemy(attackrange)) then
trans AttackState;
endif;

OrderMoveLookOut;
endstate;

state AttackState;
code
if (LeaveAttackState(withdrawRange)) then
hit = 0;
trans WaitToAmbushState;
endif;

// check to see if im getting roasted.
if ((isshot(me)) == true) then
hit = hit + 1;
shooter = Whoshot(me);
SetTarget (me,shooter);
endif;

// dam need to change my tactics
if (hit == 5) then
hit = 0;
trans attackstate2;
endif;

OrderAttackTactic(TACTIC_PICK_BEST,TRUE);

endstate;

state AttackState2;
code
if (LeaveAttackState(withdrawRange)) then
hit = 0;
trans WaitToAmbushState;
endif;

if ((isshot(me)) == true) then
hit = hit + 1;
shooter = Whoshot(me);
SetTarget (me,shooter);
endif;

// walking away to sniper range, then switching tactics if im still being hit.
if (hit == 5) then
hit = 0;
trans attackstate3;
endif;

OrderAttackTactic(TACTIC_BACK_UP_AND_FIRE,TRUE);

endstate;

state AttackState3;
code
if (LeaveAttackState(withdrawRange)) then
hit = 0;
trans WaitToAmbushState;
endif;

OrderAttackTactic(TACTIC_SNIPE,TRUE);

endstate;

state DeadState;
code
revenge = WhoDestroyed(me);
SetTargetDesirability(revenge, 100);
orderDie;

endstate;


endfsm.

The first change we made was add the following line right at the top of the script.

static locpoint defendarea;

We are basically setting up a locpoint var so its ready to use.

Next we changed the start state to this.

state StartState;

code
GetLocation(ena_ena_objective1,defendarea);
SetcombatLeash(me,defendarea,600);
trans WaitToAmbushState;

endstate;

We used Getlocation to get the position of our objective1 navpoint and assign its 3d coordinates to our new locpoint called defendarea. We then set a leash on our playersmates bots so they cannot move further than 600 meters away from the navpoint. 600m being the distance the enemy bots have to be before they can fire back. Handy isnt it :)

Look up these commands in the orders doc and learn more about them before moving on. Scripting is about learning the commands available to you. So you might as well start now.

Add a bot to the players side and set its properties like the pic below.

Where it says script, choose your playersmates script. You can change the name of the bot if you like. Remember the alignment needs to be set to a team for multiplayer.

Run a script check from the editors tools menu to make sure none of the scripts have any errors and then save and load the mission in Mercs and try it. See if both bots work correctly.

Assuming they do, its time to make this mission much bigger.

Making the mission bigger.

Copy the building in the editor by selecting it and pressing Control C. Now press Control V on your keyboard 5 times to paste 5 copies of your building. This should copy the buildings properties so you do not need to set them up again. Handy tip for you there. Now move the buildings and place them around the Navpoint objective 1.

Now select the enemy bot and copy it in the same way. Press Control V 15 times to make 15 more bots. Move them to a location about 1500m away from the nav point.

Now select the playersmates bot and copy it in the same way. Press Control V 15 times to make 14 more bots. Position them around the Navpoint.

You can safely change the building or Mechs you use by changing them from the Game Resource Type option in the above example. It is Safe in the Mercs Editor anyway, its not safe in some of the older editors. Remember me saying your working around bugs 50% of the time :)

Now run the script check again from the editors tools menu, it should still be ok. Then save the mission and try it in mercs. You should now find your selve in a 30 mech battle over 5 buildings. Nothing has changed, the objectives are still the same but its more fun now.

Other things you can do.

Mercs scripting allows you to do so many things its really just a matter of learning the commands to learn what you can do. I found it better to experiement by making test missions just to learn how something works. You know how to make objectives now, and how to assign mechs to groups to use in those objectives. So you have the basic elements required to make a mission for either single player or multiplayer.

What you do not know yet is probably the most important thing for complex missions with or without movie intros and other things. So lets clear up one Orders Doc error right now to help you when you get to a point where you want to make a intro for single player (Cool feature alert, which means it wont work right in multiplayer).

The orders doc says.

StopCinema

Deactivates the Cinema System.

Format: StopCinema

Returns: Nothing

Example Usage: StopCinema;

The command name is wrong, its real name is CinemaEnd;

Also all offset commands take their direction from the camera or object its followings current facing. So if your camera is pointing east, then that offset is 000 is east too, west would be 180 and so forth. But you will have to get used to the pitch and yaw offsets as they differ slightly and change the direction of the camera. A little messy but after some practice you will figure it out. Especially now you know the stop cinema command doesnt exist and the orientation is the cameras current facing not maps North.

Controling your Missions Flow.

You cannot do any of the fancy stuff without knowing how to control the flow of your mission. I use a simple check system because its much easier to read later rather than the state system the devs use. The disadvantage of the state system is that if a building was destroyed whilst the game was in a state that was checking where the player was. The game would never know the building was destroyed. So the mission would break. The state system is also extremely linears. Either player does it your way or the mission doesnt allow them to finish it. Not good.

The check system i use is a simple system that requires you use 1 state for your main game loop. Eg the place where all your commands are writen.

In a mission im currently working on I have this as my main game state. Its the state that controls all the objectives and the state the game spends most of its time executing. All other states leading to it and from it are one shot states. They are executed and then forgotten.

state gamestate;
code

if (check5== 0) then
if ((iswithin(playermech,ena_joke1,500)) == TRUE) then
Playvoiceover("vo\Trondheim\joke1",talker_CAS);
check5 = 1;
endif;
endif;

if (check6== 0) then
if ((iswithin(playermech,ena_joke2,500)) == TRUE) then
Playvoiceover("vo\Trondheim\joke2",talker_CAS);
check6 = 1;
endif;
endif;

if (minprot1 == 0) then
if (Groupalldead(groupobjectid(41))) then
// PlayVoiceOverOnce "Lost a lance".
PlayVoiceOver("vo\Trondheim\playerwolv1",TALKER_CAS);
PlayVoiceOver("vo\Trondheim\static",TALKER_CAS);
PlayVoiceOver("vo\Trondheim\mhqreenforcements",TALKER_CAS);
PlayVoiceOver("vo\Trondheim\static",TALKER_CAS);
FailObjective(eob_minprotect1);
RevealObjective(eob_minprotect2);
minprot4 = 1;
minprot1 = 1;
endif;
endif;

if (minprot2 == 0) then
if (Groupalldead(groupobjectid(47))) then
// PlayVoiceOverOnce "Lost a lance 2".
PlayVoiceOver("vo\Trondheim\playerwolv2",TALKER_CAS);
PlayVoiceOver("vo\Trondheim\static",TALKER_CAS);
PlayVoiceOver("vo\Trondheim\mhqreenforcements",TALKER_CAS);
PlayVoiceOver("vo\Trondheim\static",TALKER_CAS);
FailObjective(eob_minprotect2);
RevealObjective(eob_minprotect3);
minprot4 = 2;
minprot2 = 1;
endif;
endif;

if (minprot3 == 0) then
if (Groupalldead(groupobjectid(53))) then
// PlayVoiceOverOnce "Lost a lance 3".
PlayVoiceOver("vo\Trondheim\playerwolv3",TALKER_CAS);
PlayVoiceOver("vo\Trondheim\static",TALKER_CAS);
PlayVoiceOver("vo\Trondheim\mhqreenforcements",TALKER_CAS);
PlayVoiceOver("vo\Trondheim\static",TALKER_CAS);
FailObjective(eob_minprotect2);
minprot4 = 3;
minprot3 = 1;
endif;
endif;

if (minprot4 == 3) then
// Mission failed.
PlayVoiceOver("vo\Trondheim\mhqtrinary",TALKER_CAS);
PlayVoiceOver("vo\Trondheim\static",TALKER_CAS);
FailObjective(eob_Minprotectfailed);
trans missionfailure;
endif;

if (check1 == 0) then
if ((iswithin(PlayerMech,ena_Staging_Area_2,200)) == TRUE) then
PlayVoiceOver("vo\Trondheim\playeroutpos",TALKER_CAS);
PlayVoiceOver("vo\Trondheim\static",TALKER_CAS);
PlayVoiceOver("vo\Trondheim\mhqgooutpost",TALKER_CAS);
PlayVoiceOver("vo\Trondheim\static",TALKER_CAS);
SuccessObjective(eob_stagearea2);
RevealObjective(eob_stagearea2obj);
check1 = 1;
endif;
endif;

if (check2 == 0) then
if (Groupalldead(groupobjectid(100))) then
// PlayVoiceOverOnce "enemy dead".
PlayVoiceOver("vo\Trondheim\playeroutpos",TALKER_CAS);
PlayVoiceOver("vo\Trondheim\static",TALKER_CAS);
PlayVoiceOver("vo\Trondheim\mhqsupply",TALKER_CAS);
PlayVoiceOver("vo\Trondheim\static",TALKER_CAS);
SuccessObjective(eob_stagearea2obj);
RevealObjective(eob_victory);
SuccessObjective(eob_victory);
trans missionvictory;
check2 = 1;
endif;
endif;

endstate;

You can see a lot of check == 0 lines above.

These are intialised at the top of the script like this.

static integer check1;
static integer check2;
static integer check3;
static integer check4;
static integer check5;
static integer check6;

Then given their starting values in the function init like this.

check1 = 0;
check2 = 0;
check3 = 0;
check4 = 0;
check5 = 0;
check6 = 0;

Then they are ready to use.

As you can see the above example plays a lot of custom voices. This means its designed for singleplayer but multiplayer has some built in sounds it can use too. You just cannot easily customise them for multiplayer unlike singleplayer.

Now if you had an objective and you checked to see if it was completed like this then you suddenly hit a major problem.

if (Groupalldead(groupobjectid(47))) then
// PlayVoiceOverOnce "Lost a lance 2".
PlayVoiceOver("vo\Trondheim\playerwolv2",TALKER_CAS);
PlayVoiceOver("vo\Trondheim\static",TALKER_CAS);
PlayVoiceOver("vo\Trondheim\mhqreenforcements",TALKER_CAS);
PlayVoiceOver("vo\Trondheim\static",TALKER_CAS);
FailObjective(eob_minprotect2);
RevealObjective(eob_minprotect3);
endif;

The first time group 47 is found to be dead, all those voices get played. Trouble is, once they are dead, they are always dead. So each time the game asks are they dead the answer is always going to be yes. So guess what happens. Yup those voices get played over and cover again.

This is the problem with a single state controling the entire game. So we need some checks to make sure its played only once. I could easily use Booleans for this but I prefer integers because occasionally i go higher than 1 in other checks. EG check5 goes up to 3 in one mission I made because I used it to control the order some events occured in.

Now this example uses a check that only allows it to run once.

if (check1 == 0) then
if (Groupalldead(groupobjectid(47))) then
// PlayVoiceOverOnce "Lost a lance 2".
PlayVoiceOver("vo\Trondheim\playerwolv2",TALKER_CAS);
PlayVoiceOver("vo\Trondheim\static",TALKER_CAS);
PlayVoiceOver("vo\Trondheim\mhqreenforcements",TALKER_CAS);
PlayVoiceOver("vo\Trondheim\static",TALKER_CAS);
FailObjective(eob_minprotect2);
RevealObjective(eob_minprotect3);
check 1 = 1;
endif;
endif;

Now we setup the checks above, initialised them and set their value to zero. So the first time this is run the check is actually zero. It then checks if the groups dead, if its not it skips the rest of this section and check continues to = zero. If it is dead it plays all the voices and then gets to the check1 = 1 line and sets check1 to 1. Next time the script reads this it goes to the line "if (check == 0) then" and finds that check = 1 and skips the entire section. Thus making sure its only ever run once. Never twice.

This is the method I developed for the Banzai Training missions back in the BK days to allow me to run several training ranges at the same time and known the game script was looking after them all and not ignoring one whilst it was in the state for another training area. Its also used in every mission and gametype ive writen since then.

You can turn movie sequences on or off using checks like these. You can even change check1 which is 1 after the above section is run the first back to 0 elsewhere in the script so it will run a second time.

It gives you maximum control over what your script is seeing and checking for and you know its not blind half the time like the dev state drive scripts are.

The secret to getting sounds and movies or even objectives to work exactly when they should no matter what the player may do is by using checks like these. So know you know. :)

Such a system makes your missions more dynamic and more able to cope with what the player does.

Finishing up.

There is no single way to write a mission. There is no making a mission without knowing a little about scripting for Mechwarrior4. However, you can make missions by editing scripts that already work such as those available in the Mechstorm.net download area. Often only a small edit is required to make the script usable in your mission and the edit you need to make depends on what you want to do.

When I make some bot scripts for general release im often making a script for a mission but im making a version that will work on any map first, then testing it to be sure it will work and release it as part of the DSRP collection of scripts. By the time I make the mission I have a collection of scripts I know already work and I just edit those to suit the mission. I do not write brand new scripts for a mission unless I really have too. All that is done before I start making the mission, at a time when im not distracted by mission objectives or master mission scripts. Master scripts btw are nearly always custom scripts. Use once and forget. Unless they are gametype scripts.

The trouble is most people do not know what is possible so they cannot say what they want their missions to do. Once you do know what is possible you will find you self just looking up the command to do the job and then working out how to limit it so it runs only once. Whilst learning what is possible you spend most of your time reading the Orders doc, after you learnt whats possible you spend most of your time looking up the commands in the orders doc. Either way your face is buried in the same document most of the time.

Using the commands in the orders doc is easy, but you must learn what they do and what commands exist to help you run your mission. After that its all checks and loops as described above. Learning how to keep your missions dynamic yet under tight control is the key to making a successful mission.

So the most important things to learn after you learn the commands is how to control when and where those commands are executed and the check system above is the method I use for this because its very easy and allows me to easily add thing without having put add hundreds of checks. Lets face it, check1 can just as easily = 340 as it can = 1.

Good luck on your mission making.

Remember we are there in the forums for you if need help.

Giskard