BZII DLL Scriptor
User's Guide
(Version 1.1)

[Script Operations]

The BZII DLL Scriptor is a handy utility that a map maker can use to create a customized BZII instant action mission. The user creates an input file consisting of predefined commands, and the BZII DLL Scriptor processes those commands to create a mission DLL. The user does not need any other utilities to make the DLL.

For most mission scenarios it's somewhat easier to generate a DLL with this utility than it is to create one using a Visual C++ compiler. However it's still no piece of cake to create a decent DLL-driven mission using this utility. It can take a lot of work to generate a lengthy complex mission. For ease of use, these instructions and the script operation instructions should be used right alongside the BZII DLL Scriptor Utility.

 

Interface and Script Editor

The BZII DLL Scriptor is a Windows style program with an integrated text editor. This editor opens and saves script files with a .bzs extension. Above the editor window is a toolbar with a "Build" button and a drop-down selection box. This box displays all of the available script operations. You can select a script operation, and the operation keyword will be placed in your script file where the cursor is. You should note that this actually performs a copy and paste using the system clipboard, and therefore anything that was copied to the clipboard will be replaced by the operation keyword that you chose to add to the script.

Clicking on the "Build" button brings up the build dialog that is used to translate your script into a DLL. Click on the "Create" button to perform the translation. Note that any unsaved changes to your script are automatically saved when you click on the "Create" button. When it's translating, it will post information to the message window of this dialog. If it finds an error, it will post information about the error in the window. When enabled, the "Go To Error" button causes the cursor to move to the line of the script where the error was found. The translator aborts as soon as it finds an error. If the operation is successful then the resulting output is placed in the same folder as the script file.

Getting Started

When designing a mission, it's best to make a sketch of the paths and their names, and to note the locations and labels of any objects that you place in the map. It may also help if you jot down a plan of events on the map. Sketching it out can help you plan a more effective mission. Having the path names and object labels written down will make them readily available when writing your script.

For a DLL to be compatible with a mission, the DLL's full name must be placed into the mission using the BZII map editor. To do this, go into the editor mode, and select the "Path" button. In that view there is an entry box where the DLL's full name is placed.

It's important to remember that all DLLs must be placed in the Missions folder, whose root is the Battlezone II folder. It may be convenient to place the BZS script file in the Missions folder while you develop the script. That way, the DLL will always be placed where it needs to be placed whenever you create it using the DLL Scriptor.

Script Format

A script is written as a text file. It contains 4 sections for declaring information about your mission. These sections are as follows:

[objects]
[positions]
[variables]
[text]

[Objects]

In the [objects] section you can make a list of names for objects, buildings and units that will play an important part in your mission. You can use any name you want for an object as long as it is continuous, with no spaces. When your script creates an object or unit in your mission, you will use one of these declarations as your object's name. That name now belongs to that object until it is destroyed, or until you associate that name with a different object. There are other script commands that take an object name and apply it to a specific object in the mission. In that case, the name now belongs to that object. Sometimes you only want an object associated with a name temporarily so that your script can give that object a command. You can then use that name on a different object so that you can give it a command, and so on.

The DLL does not know about objects that were placed in the mission using the BZII map editor. To associte an object name with an object that is already in the map, you must use the GetByLabel operation. You also must give that object a label using the BZII editor. It's label usually defaults to something like "unnamed_...". You can change it to any name whose total length is 19 characters or less with no spaces. The capitalization of the label must precisely match that of the script string between quotes. For example, if you labeled a game object "Warrior_1", the script command must be GetByLabel,Attacker,"Warrior_1" in order to associate that object with the Attacker object name.

There can be up to 64 object names in the [objects] section. They can be declared as single objects, or as a grouped arrays of objects using the <> characters, i.e. Attackers <6>. Here's an example of how an [objects] section might look:

[objects]

IndexedObject
Attackers <6>
Recycler
Construcor
Escort1
Escort2
Matriarch
Forge

[Positions]

In the [positions] section you can declare positions that you plan to use in the game for placing objects or setting up camera sequences. A position is initialized to the three numbers that come after its name in the declaration. Other script commands can change these positions. The following is an example of a [positions] section.

[positions]

SpawnPoint1,150,0,100
EnemyBase,0,0,0
Camera1,0,-20,10

[Variables]

In the [variables] section you can declare numerical variables that can be used as counters and other useful data storage. The variables are initialized to a value that will be in effect at the start of the mission. The following is an example of a [variables] section where each variable is initialized to 0.

[variables]

counter1,0
counter2,0
enemycount,0
Index,0

[Text]

In the [text] section you can declare your mission objective text that can be displayed using the display script operation. The text for each objective statement must be enclosed in quotes. You must assign a name to each statement. This name must be referenced in the display command. Each statement may contain up to 149 characters and spaces (including any necessary new line characters). The following is an example of a [text] section.

[text]

DefendRecycler,
"Defend the approaching recycler."

DestroyBase,
"Destroy the enemy base to the\nnortheast."

You may need carriage returns in some of your objecive text. Put a backslash and a lower-case n where you want the carriage return to occur. Actual carriage returns should not be placed within the script text itself.

Dropdown Keyword Selector and Help Window

The handy keyword selector lets you select any of the available operations and place it in your script. The gray box to the right shows the format for the input arguments when an operation keyword is selected. To place the word in your script, click on the word in the list. To select a word without placing it into your script, hold the pointer over the word, drag to the right or left so that the pointer moves off the list, and then click the mouse button.

The utility has a handy help window that can be viewed right alongside the script text. The window defaults to the HTML document that describes all of the operations. There is a splitter bar that seperates it from the script text. If the splitter bar is all the way to the right, then only the script text will be visible. In that case the help window can be seen by moving the cursor to the splitter bar and dragging it to the left.

The help window is empty when the program first launches. To bring up the HTML help document simply click on the Help button. Once it is opened, the Help button can be used to quickly view the details of the selected operation. If the drop down keyword selector has an operation displayed, click on the Help button to quickly go to the details of that operation in the help window.

Routines

A script contains one or more routines to make things happen in a mission. You fill each routine with script operations that cause certain events to occur. Most of these operations require certain parameters to function properly. When a mission starts, all routines that you've written with a run speed greater than zero start running. For a routine that has a run speed of 1 and is running at high priority, the DLL typically does 1 of its operations every one tenth of a second.

Up to 20 routines can be created in a script. The total number of operations in all routines together must be less than 1000. This includes an invisible operation that is tacked on to the end of each routine that causes it to stop running when the end is reached.

A routine is declared by giving it a name, and setting certain properties that you want to be in effect when the mission starts. The first property is the run-speed that you want the routine to start at. This should be 0 if you want the routine to be off when the mission starts, or it should be 1 or more if you want it to be running. The number determines how many of the routine's script operations are performed every time it has a turn. It can be set to higher values, but this should be avoided unless you have a good reason for having the routine perform more than 1 operation every time it has a turn.

The second property is the priority that you want the routine to have. If this is set to true then the DLL will always give that routine a turn every time the DLL is called, about 10 times per second. If it is set to false, then the routine will have to wait in line with all the other low priority routines that you write. By default the DLL takes care of up to 2 low priority routines every time it is called. If you only have 2 low priority routines, then the DLL would give each one a turn every time it runs. If your script has 20 low priority routines however, it will perform their operations about once every second. Therefore the more low priority routines in your script, the slower those low priority routines will execute. The default number of 2 low priority routines per DLL call can be overridden with the globalspeed command.

If you have numerous routines in your script that don't need to run very fast, you should set their priority to false so that the DLL doesn't have a noticible impact on performance.

The following is a simple example of 2 routines, where the first number after the routine's name is the run-speed and the boolean that follows is the priority.

[routine,Main,1,true]

wait,5

SEND_ATTACK:

create,Attacker,"fvtank",5,spawnpoint1
attack,Attacker,MyRecycler,1
wait,30
jumpto,SEND_ATTACK

[routine,CheckRecycler,1,false]

getbylabel,MyRecycler,"isdfrecycler"

RECYCLER_IS_ALIVE:

isaround,MyRecycler
ifeq,true,RECYCLER_IS_ALIVE
fail,10,"failtext.des"

In this example, the first routine called "Main" starts when the mission starts, and at high priority. It waits for 5 seconds, and then creates a Scion warrior to attack the player's recycler every 30 seconds by looping back to the SEND_ATTACK script label. The second routine called "CheckRecycler" also starts as soon as the mission starts, but at low priority. It determines which object is the recycler based on a name given to the recycler using the map editor. It then continues to check and see if the recycler is still around and if it is it loops back to the RECYCLER_IS_ALIVE script label to check again and again. If the recycler is destroyed, this routine will stop looping and will cause the mission to end in failure in 10 seconds, after which it desplays the fail text in file "failtext.des".

Script routines can be running in parallel, such that you can have up to 20 scripts running at the same time. This is useful if you want numerous routines to be running while your main routine is also running. For example you can have numerous routines that are constantly looping to check if certain units are still alive. These routines would be acting as triggers, and can make something else happen of the object is destroyed. You can also use other routines to handle camera events so that your main routine doesn't have to wait for the camera sequence to end.

Routines can turn themselves and each other on or off by using the RunSpeed operation. A routine can also reset another routine to the beginning or any other part of that routine using the SetStep operation. These can be useful if you don't want a routine to start until a certain point, or you want to cause a routine to start over.

Operations that Pause a Routine

While most script operations occur fairly quickly, a few are designed to hold up a routine for seconds or minutes. The Wait operation will pause a routine by the specified number of seconds before allowing it to continue. Camera operations will also pause the routine until they are finished. The camera operations are: CamPos,CamPath, CamPathDir and CamObject. It may be best to use a dedicated routine for camera operations if other things need to occur at the same time.

Control and Branching to Different Parts of a Routine

For most script operations, the DLL moves on to the next one below it after performing the operation. By design the DLL only does a piece of each script at a time, so you don't have to worry about making the DLL return control to the game program. This happens automatically with the Scriptor DLL design. Once the DLL gives each routine active routine a turn, it let's the BZII game program do its thing.

There are certain types of operations that can cause a jump to another operation in the same routine. Some of these operations begin with "IF", and only cause the jump if certain conditions are met. They will simply let the next operation occur if the condition is not met. Another operation that causes a jump is the JumpTo operation, which always causes the routine to jump to another location.

These are considered flow control operations, and they use label names to tell them where to jump. The labels that they reference are placed just before the operation that is to be performed after the jump. The labels themselves (not the references) must be followed immediately by a semicolin.

The operations whose keywords start with "If" use the result of a prior operation to determine whether or not to jump. There are numerous operations that generate a result for these operations to check. For example, the add operation leaves behind the result of the addition so that a flow control operation can check it. Here's a set of operations that causes 6 Scion Warriors on the enemy team 5 to be created at Position1:

Set,Counter1,0

MAKE_WARRIORS:
Create,Attacker,"fvtank",5,Position1
Add,Counter1,1,Counter1
IfLT,6,MAKE_WARRIORS
Wait,30

Whenever a Warrior is created, the Counter1 variable is increased by 1. It is then checked to see if it's still less than 6. If so, the IfLT operation causes the routine to jump back to the MAKE_WARRIORS label (followed by a semicolin) so that yet another Warrior can be created. Once Counter1 is increased to a value of 6, the IfLT operation lets the routine move on to the next operation: Wait,30.

Comments in a Script

You can place comments anywhere in your script after placing 2 forward slashes. Everything between the 2 forward slashes and the end of the line will be ignored by the utility.

Preloading ODFs and Audio Files

Some ODFs, such as those of factories, recyclers and constructors, can freeze the game for a second when the object is added during a mission. Audio files can also have this effect when they are played. The way to avoid this momentary freeze is to have the object or audio file preloaded before the mission starts. A feature of the BZII Scriptor is that it allows you to specify which ODFs and audio files you want to be preloaded. For ODF files you can do this by putting a '>' symbol before the ODF name in your script. An example is:

create,MyConstructor,">ivcons",1,ConstSpawn

For audio files you can do this by putting a '*' symbol before the ODF name in your script. An example is:

audio,"*briefing.wav"

**WARNING** Problems have been observed when pre-loading sounds and then playing them. The game stops playing sound files after playing the preloaded sound. This is a game issue, not a BZII Scriptor problem. The ability to pre-load sounds has been kept in case the proper use if this feature is discovered.

If you are referencing a preloaded ODF or audio file more than once, it's best repeat the exact same text, including the symbol. For example, a constructor ODF will be preloaded only once even if you use the line: create,const,">ivcons",5,constspawn many times in the script. The scriptor translator sees that ">ivcons" is used multiple times and will store the word only once. If you make one of the letters capitalized or you have another occurence of "ivcons" without the '>', the translator will interpret it as being different and you'll be wasting an extra text string. Remember that the text must be 19 characters or less, including the symbol.

You should only use the '>' symbol at the start of an ODF name. Likewise you should only use an asterisk '*' symbol at the start of an audio file name. You may want to use the preloading feature sparingly in some cases. A lot of preloading can make for a very long mission startup time.

Enemy Unit Construction and AIP Files

A DLL cannot directly command a recycler or factory to construct units. This is done indirectly by loading an AIP file using the SetPlan operation. Once your script creates and deploys the enemy recycler, you can load the AIP file and it will take over from there as long as you wrote your AIP file correctly. The AIP file can also have a constructor build the enemy base although certain path points may be required for ISDF base construction.

The complexities of AIP files are beyond the scope of this documentation (and are beyond me as well), but there may be information on the topic available at some websites and message boards.

On Creation and Destruction of Objects in the Game

There are operations that can cause certain routines to be triggered when objects are created and destroyed. The OnNewObject operation sets up a routine to run when any object is created. The OnDelObject sets up a routine to run when any object is removed from the mission, i.e. destroyed. The parameters needed for either operation are the desired run speed, the routine name, and the object name that is to be associated with the object in question at the time of the event. Here is a simple example that would give a blast cannon to any Scion warriors created during the mission:

[routine,Main,1,true]
OnNewObject,10,WarriorBlast,NewGuy

[routine,WarriorBlast,0,true]

CHECK_FOR_WARRIOR:
IsODF,NewGuy,"fvtank"
IfEQ,false,AFTER_WEAPON
GiveWeapon,NewGuy,"gblast_a"

AFTER_WEAPON:
RunSpeed,WarriorBlast,0,true
JumpTo,CHECK_FOR_WARRIOR


It is very important to understand how this mechanism works to use it effectively. Whenever this routine starts up it should quickly shut itself off or all other new object that are created will be ignored while it is running. Notice that the
JumpTo,CHECK_FOR_WARRIOR command is placed directly after the operation that turns off the routine. This will cause the routine to jump back to the beginning the next time an object is created. Otherwise the routine would reach the end and nothing would happen when subsequent objects are created. If you want the routine to do other things to new objects after awhile, you can have the routine check for some condition every time it runs and then continue when that condition is satisfied. It is important to note that the routine always starts up just after the operation it last performed in the previous run.

Note also that the chosen run speed in the OnNewObject command is 10. This will cause the operations to execute very quickly when the object is created (or destroyed in the case of the OnDelObject operation). The routine stops short of doing 10 operations if it shuts itself off.

Creating and Controlling Groups of Objects

In some cases a map maker may want the script to create a group of objects, and then later send them to do something. Two operations that can help accomplish this are GetByIndex,object,object,variable and SetByIndex,object,object.variable. Be forwarned that one must be very careful to use these two operations correctly or they can really mess up a mission. The following example creates a group of Titans (fvatank) and associates them with an object group declared as Attackers <6> in the [objects] section (as shown in the script format section). It then sends those objects in for an attack.

[routine,Main,1,true]

Set,Index,0

CREATE_GROUP:
Wait,40
Create,IndexedObject,"fvatank",5,ForgePos
SetByIndex,Attackers,Index,IndexedObject
Add,Index,1,Index
IfLT,6,CREATE_GROUP

Set,Index,0

SEND_ATTACK:
GetByIndex,IndexedObject,Attackers,Index
Attack,IndexedObject,Recycler,1
Add,Index,1,Index
IfLT,6,SEND_ATTACK

Note that in the first loop, an intermediary object item IndexedObject is used in creating the object. The association is then copied into the Attackers <6> group array using the SetbyIndex command, and as the Index variable is incremented each of the 6 entries in the array group is associated with the Titan that is created in each pass.

Once the 6 Titans are created another loop sends in the attack using the GetByIndex operation to access each of the 6 entries in the Attackers <6> group. On each pass the GetbyIndex operation copies the association from an entry of the group to IndexedObject.

The group array name, i.e. Attackers, can be used as in operations as if it is a single object. In that case only the first object in the group will be affected.

The first entry in the group is always referenced by an index of zero. For an object array that contains 6 entries the last entry would be accessed if the Index variable is equal to 5. If the indexed variable were higher than 5 using the SetByIndex operation, then the objects declared just after the Attackers <6> declaration might overwritten, and their previous associations lost forever. Similarly, using the GetByIndex with an indexing variable that is too large can result in objects declared after the array to be the recipients of commands. That is fine if you want to deliberately do that, but it can be the source of frustration if it was unintended. An example where this can be done deliberately is if the [objects] section appeared as:

[objects]

IndexedObject
Mauler
Warrior
Titan
Sentry
ScionScout
Constructor

And after the commands that create the objects the remainder of the routine could be written as:

Set,Index,0
ALL_ATTACK:
GetByIndex,IndexedObject,Mauler,Index
Attack,IndexedObject,Constructor,1
Add,Index,1,Index
IfLT,5,ALL_ATTACK

In this example there is no group array declared. The GetByIndex command copies the Mauler association to IndexedObject when Index is 0, and then copies the Warrior association to IndexedObject when Index equals 1 and so on. Keep in mind that if Index was equal to 5 when the GetByIndex operation occurs (i.e. the last line was IfLT,6,ALL_ATTACK), the Constructor would be the recipient of an Attack command on the last pass through the loop. In this case it would be commanded to attack itself!

This approach to manipulating groups of objects can be tricky and a bit confusing, and great care must be taken so as not to affect other objects. Future versions of the BZII DLL Scriptor may (hopefully) simplify the creation and manipulation of object groups.

Capitalization of Item Declarations

Capitalization of the operation keywords has no affect on a script, as long as the spelling is correct. However, for all items declared in the header sections, the capitalization (and of course spelling) in the script must exactly match the capitalization of the item declarations or the utility will flag it as an error. Capitalization of script labels must exactly match the references to those labels.

Errors

Script error messages can be frustrating. However the BZII DLL Scriptor must catch certain errors in order to correctly process a script.

The BZII DLL Scriptor will abort on the first error it finds. You can then use the "Go To Error" button to place the cursor on the error. Errors may be the result of a misspelling, leaving out a quotation mark, a name in quotes that is longer than 19 characters or data that doesn't match what's in the header.

Another type of error is one that the BZII DLL Scriptor cannot catch. Path names must exactly match the path name in your map, including capitalization. For example, if you path is labelled "path_1", but in your script you refer to it as "Path_1", the script command will not work because of the capital 'P'. The same goes for labels that you give an object in the BZII map editor. If you label an object "tank_1" in the BZII object editor, but you want to get access to the object using the command getbylabel,friendtank,"Tank_1", the script command won't work because of the capital 'T'.

Still another error that can't be detected by the utility is the case where an object indexing operations unintendedly affects an object, i.e. the indexing variable was allowed to get too large.

Keep in mind that even the best compilers can sometimes post ambiguous error messages. The BZII DLL Scriptor is no exception, and can sometimes post an error message that's hard to understand at first. It's also possible that the translator will indicate an error on a certain line when the real error is on the line before it. If you can't find an error on the specified line, look closely at the previous line in the script.

Debug Version DLL and C Code Output

The build dialog has some alternate options. One of those is the option to generate a debug version of a DLL, which will output to the BZII console for all routines that have the debug parameter set to "true". This is enabled using an extra parameter in the RunSpeed operation, or in the routine header. The console output won't contain any names that match object names, position names and variable names. Instead it displays the numbers that represent those items. However it will display text strings such as ODF names, path names etc.

Another feature is the ability to generate C style arrays of all the data that is used by a generated DLL. This output can be merged with the DLL source code that will be available seperately, and allows programmers with a Visual C++ compiler to mix generated scripts in with their own code, and to customize the code and modify the behavior of operations if needed.

Getting Help and Reporting Bugs

The best way to seek help if your having minor problems is to check web sites and post on the various message boards in the Battlezone community. the following are links to a few sites that may be helpful:

Battlezone Launch Pad

The home website for the BZII DLL Scriptor

The BZ2 Unofficial Editing Forum

This is a forum for topics relating to BZII map making.

Insomniax Message Boards

A BZII Map Making forum, as well as other cool forums.

Unfortunately the creators of this utility will probably not be able to reply to every request for help. However, if you're at your wit's end, you've tried everything and it's a dire map-making emergency, the e-mail address at the Battlezone Launch Pad may (in time) get you a response. We just don't have time to offer widespread support.

If you think you've found a bug then feel free to let us know at the Battlezone Launch Pad Address.

Limitations

Making a DLL with the BZII DLL Scriptor has its limitations. It would take a pretty large script to reach some of these limitations, but it is possible. Here's a list of the significant limitations.

Even though 200 text string references is more than a typical script would need, it's a good idea to make sure that you use them efficiently. If you reference an odf name or file name more than once, its best to use the exact same capitalization. That way the translator will see them as identical and will use only 1 text string to store the word. If there are differences in capitalization, the translator will store the different versions as different text strings.