Fallout scripting language tutorial
The syntax of the Fallout scripting language is a mix of C preprocessor and Pascal, but it is a very generic language, hence you do not need to know either to be able to write scripts. A modicum of experience with any programming language will make things a lot easier, but is not a requirement, in fact this tutorial will contain some information that many programmers already know. During this tutorial I will always assume that you are simultaneously also reading the mapper manual and that you are reading through existing scripts for clues as to how something works.
This tutorial is not a step-by-step tutorial, where I tell you what to do and you copy me. It is rather intended as an introduction to the language, examining what you can and can't do. This is for two reasons: 1) step-by-step tutorials are boring and 2) step-by-step tutorials are generally useless after you've read them one time. This tutorial will not include any advice on how to compile your scripts, how to add them to critters (or maps, or hexes) in the game, or how to install or use any software (which includes compilers, various modding tools etc. and unpacking dat files). If you need help with the above issues, try reading the relevant manuals and readmes. If that doesn't help, try posting to the Fallout modding forums at the Interplay forums or No Mutants Allowed.
All of the game specific commands that are used in the scripting language can be found in the manual that comes with the Fallout 2 mapper, beginning on page 9. The manual is quite clearly organized, but here is a rundown of how to read it :
The box to the right is the description of the command. Read it carefully.
Bold text on the left : the name of the command. To use a command in a script, just type it as it is written in the manual. Pretty obvious.
Italic text, below the name : the return type of the command. A command can return either nothing, or an object of some type. The most common types are int, an integer and void, which means that the command returns nothing. Typically, commands that don't return anything do something to the state of the game, and commands that return something want to know the state of something in the game.
Normal text to the left, below return type: what arguments the command must be passed. Most commands take arguments. The arguments are usually well described in the description box. Arguments are written within parentheses after the name of command, and multiple arguments are separated by commas, like this:
The arguments must be of the correct quantity, they must in the correct order, and they must be of the correct type (if the command is expecting an integer, you cannot pass it anything but an integer)
A command followed by a semicolon (;) constitutes a statement. All commands must be part of a statement or expression. Note that you can use the return value of one command as the argument to another command:
In addition to these game specific commands there a number of more generic commands:
#define name (value) #define macroname body
The #define command is used to create a macro. You can think of a macro as an alias. In the example definition, name would be an alias for value. More about macros in the macro section. (Note: actually, both of the above commands define macros)
The header file is included in the script and the macros in it can now be used. The header files are located in the folder scripts\headers in the directory in which you installed the mapper. You can also make your own header files. Almost all scripts will include define.h and command.h. (Note: if you want to know more about #include and #define, you should read the C preprocessor language reference that is probably included with your C compiler.)
procedure name begin body end
A procedure definition. More about procedures in the procedure section.
Used to call a procedure from within another procedure. Must end with semicolon (;).
Creates a temporary local variable with the specified name. Values are bound to the variable with := . Ex. variable killcount:=0. This kind of variable is not saved between map changes.
The if-then-else statement is sufficiently important to deserve a chapter on its own. It is a basic statement to control the flow of the script. There are a couple of different ways to write this statement in the language.
if () then begin do something; end
This is the standard form of simple if. The parentheses following if must hold an expression that translates to a boolean value. A boolean value can only assume two values: true or false. If the boolean value within the parentheses is true then the statements between begin and end will be executed. If it is false the program will directly jump to the statement following end.
(Note about boolean values: There are no real boolean values in the scripting language. Like in C, an integer represents true or false. 0 is false, anything else is true.)
The expression within the parentheses must as said before, translate to either true or false. It can either be a boolean value, a command that returns a boolean value, or a boolean value obtained by comparing two values. The following are the legal comparators.
> (greater than) < (lesser than) >= (greater than or equal to) <= (lesser than or equal to) == (is equal to) != (is not equal to)
The test for true or false can also check for multiple conditions by using the terms and and or. If you want the code block between begin and end to run when the test returns false instead of true you can preface the test with an exclamation mark (!). You can also use it to negate the value of a single condition within the test. Examples of valid true/false tests.
if (dude_is_male) if (is_success(repair_check)) if (skill_used == SKILL_REPAIR) if ((is_success(repair_check)) and (skill_used == SKILL_REPAIR)) if (!is_success(repair_check)) if (is_success(repair_check) and !(dude_is_male))
The basic if can be expanded to include an else part that will execute if the if test fails:
if () then begin do something; end else begin do something else; end
The if-then-else statement can also be chained:
if () then begin do something; end else if() then begin do something else; end else begin do something else; end
The scripting language also supports loops, although these are generally not very useful, since scripts rarely need loops. But in case you need one, the syntax is as follows:
while (condition) do begin body; end
Where condition stands for a boolean value, just like in the if statement. The first time the while loop is entered, the condition is checked. If it is true, then the body is executed. Now the condition is checked again, if it is true, the body is executed again. This is repeated until the condition is false.
procedure name begin body end
The body between begin and end consists of the statements the procedure will execute. To do this the procedure must be called:
Most of the procedure calls will be made by the Fallout engine, hence you will rarely have to worry about calling procedures yourself. The procedure calls that are handled by the engine are listed in the mapper manual on page 25. In addition to those listed in the manual, there are a number of new ones that were probably added to Fallout 2. I've only found push so far. If anyone finds anything else, let me know or add it to this page. An example of a procedure that is called by the engine:
/* This procedure will get called each time that the map is first entered. It will set up the Team number and AI packet for this critter. This will override the default from the prototype, and needs to be set in scripts. */ procedure map_enter_p_proc begin Only_Once:=0; critter_add_trait(self_obj,TRAIT_OBJECT,OBJECT_TEAM_NUM,TEAM_KLAMATH); critter_add_trait(self_obj,TRAIT_OBJECT,OBJECT_AI_PACKET,AI_TOUGH_CITIZEN); end
In addition to the procedures that are handled by the engine, you can also write your own procedures that you will call manually. It is generally a good idea to write a new procedure if you find you have lots and lots of duplicated code in your script. For example:
procedure give_items begin give_pid_qty(dude_obj,475,3) give_pid_qty(dude_obj,159,1) end
Every time you call this procedure, the player character will receive 3 items of itemtype 475 and one item of itemtype 159. You can call it as often as you need:
call give_items; call give_items;
The above would repeat the statements in the give_items procedure twice.
All procedures that the script uses must be listed at the top of the script file, like this:
procedure start; procedure critter_p_proc; procedure pickup_p_proc; procedure talk_p_proc; procedure destroy_p_proc; procedure give_items;
In technical terms, this means that you must declare your procedures before you can define them.
Macros are defined using #define. Ex:
#define NOption(x,y,z) giQ_Option(z,NAME,x,y,NEUTRAL_REACTION)
Basically, this means that you can write NOption(x,y,z) instead of the considerably longer giQ_Option(z,NAME,x,y,NEUTRAL_REACTION). Both commands will do the same thing, though. It is wise to define macros for long command chains that are often used. The above is a common dialogue command, for example. Arguments to macros should be kept simple (i.e., single variables); complex arguments can cause strange and unexpected bugs.
If you've read through some of the scripts supplied with the mapper, you may have wondered about all the commands in them that you can't find in the mapper manual. Well, they are macros. Most macros can be found in the command.h header file. It is also preferrable that you add your own macros to this file. To use a macro in your script, you must either #define it in the script, or define it in a header file, and then #include that header in your script.
Macros may seem similar to procedures, but they are two different things. They basically behave differently from the perspective of the compiler and the computer. Procedures have to be called, macros basically just replace one bit of text with another. For example, during compilation, the preprocessor will replace all instances of NOption(x,y,z) with giQ_Option(z,NAME,x,y,NEUTRAL_REACTION). A procedure call on the other hand will push an activation record for the procedure on top of the stack and jump to that routine (during run-time). In laymans terms this means that macros are usually slightly faster than procedure calls, but procedure calls take up less space. If this last paragraph didn't make any sense, don't worry. Just remember that macros != procedures.
Bits and PiecesEdit
There is a little document that is provided with the mapper that may be of some use to scripters. Please follow the advise in it to make your scripts easier to understand for other people (some of the info may be out of date, but it is a good starting point). It is called SCRIPTS.TXT and it is located in the scripts\docs directory in the mapper installation directory.
/* This is a multi-line comment. */ call /* This is an in-line comment */ function // This is a one-line comment.
As you can see multi-line comments have to be within brackets, while one-line comments only have to preceded by two slashes (//). In-line comments must be within brackets.
It is important that your code is properly indented. Unindented code is easy to write, but next to impossible to read. Code within procedure and if-blocks should be indented at least two spaces to the right. All code that is executed sequentially should begin on the same line. Branching should be indicated by indentation of at least two spaces to the right. An example:
procedure name begin if() then begin do something; if() then begin do something within the other if-block; end end do something; do something; end
To use your scripts in the game you need to update scripts.lst, which can be found in the game directory under data\scripts, and scrname.msg which is found under data\text\english\game.
Any global variables you #define and use must also be put into a file called VAULT13.gam which is found in the data\data directory. Map variables need to be added to a file called mapName.gam which must be put in the data\maps directory.
For writing scripts I recommend you get a better editor than wordpad. I can recommend NoteTabLight to just about everyone. It is very powerful, easy to use, and it is free. If you think you're something of a hacker I recommend Emacs, which is even more powerful. But learning to use Emacs is harder than learning scripting, hence I don't recommend this to the average scripter. There are also at least two editors that are designed for Fallout scripts. One is my own Dscript, and the other is Jargo's FSE http://www.mody-jarga.host.sk/fse/fse2.htm.
I hope this has been of at least some use to someone out there. I know many will be disappointed, because I don't hold your hand and guide you in writing a simple script. Like I explained in the beginning, I don't believe in such a teaching method. I believe in a more holistic approach.
Give a man a fish and you feed him for a day. Teach him how to fish and you feed him for a lifetime.
Original article by Daniel Sjöblom (dsjoblom (at) mbnet.fi)