The use of scripts in Morrowind's Construction Set allows the plugin creator to perform a large amount of things within the game but, unfortunately, successfully writing scripts can be a difficult chore, even for the experienced programmer. Morrowind's scripting is limited and not very powerful compared to some of the scripting available in other games, but with practice you'll find that you can still produce wonderful effects from it (such as a working chess game).
To start a script open the Gameplay-Edit Scripts menu in the Construction Set to open the script window. Select the Script-New... menu to begin a new script and enter something like the following:
begin TestScript01 end
The TestScript01 will become the name of the script and it must be unique (no other script can exist with the same name). To assign a script to an object open the window to edit the object and select your new script in the script drop down list. You can assign a script to more than one object, though you must design the script properly.
Anything after the first semicolon (;) on a line will be ignored. Use this to create comments that describe what your script is doing, for example:
; Remove the sword from the player after 10 days if ( NumDays > 10 ) player->RemoveItem, "my_new_sword_01", 1 endif
Variables are used to hold various sorts of information. The scripts in Morrowind can have three different sorts of variables:
- short : Integer values from -32768 to 32767
- long : Integer values from -2,147,483,648 to 2,147,483,647
- float : Decimal values 3.4e +/- 38 (7 digits)
It is a good habit to declare all variables used in a script at the top, for example:
begin TestScript02 short DaysPassed long SecondsCalc float PlayerXPos ; etc...
Variables declared in this manner are known as local variables and belong to whatever object the script is attached to. You can also create global variables in a similar manner using the Gameplay--Globals... menu in the editor. Note that variables always default to the value of 0 before they are used or defined.
In order to set the value of local or global variables, use the set command:
short DaysPassed long SecondsCalc float PlayerXPos set DaysPassed to 0 ; Use a literal value set DaysPassed to ( DayPassed + Day ) ; Simple addition of two variables set SecondsCalc to ( DaysPassed * 86400 ) ; Multiplication of a variable and a number set PlayerXPos to ( player->GetPos, X ) ; Using a function set objectID.varName to 100 ; Setting a variable belong to another object set "object ID".varName to 100 ; Use quotes for object IDs with spaces in them
You can set variables to literal numbers, to other variables (local/global) or perform simple arithmetic (+, - ,* or /). You can also access variables belong to other objects by using the objects name followed by a period and the variable name.
The if statement is a conditional test which allows you to perform actions only if certain conditions are met, for example:
if ( DaysPassed <= 3 ) MessageBox, "Not enough time has passed" elseif ( DaysPassed <= 10 ) MessageBox, "You feel the time approaching" else MessageBox, "The time is NOW!" endif
You can, of course, have more than one line in the if statements and can put if statements within other if statements as well. You can use the tests
- == : Is Equal To
- != : Is Not Equal To
- < : Is Less Than
- > : Is Greater Than
- <= : Is Less Than or Equal To
- >= : Is Greater Than or Equal To
While statements are loops; it will execute the content of the loop until the boolean at the beginning returns false.
short Value while ( Value < 10 ) set Value to ( Value + 1 ) endwhile MessageBox, "Value:%d", Value
This will return "Value:10", because once Value is >= 10 it breaks out of the loop. Why is this useful? It is very handy for running sets of commands multiple times in one frame. The previous script without the While would take 10 frames to finish. A real-world example would be using AddItem with a large number; there is an upper limit (32767?) to how many items you can add at once. Such a script could look like this:
float Gold while ( Gold < 100000 ) player->AddItem, Gold_001, 100 set Gold to Gold + 100 endwhile
Don't use this script for anything, though. It won't work for values that aren't a multiple of 100, and if you lower the number of items you add at once it'll take much longer to do all of it. Speaking of which, it's good to note that while loops basically pause the game while they're going. If the initial boolean is never false then the game will freeze up entirely; try to avoid this!
Morrowind has a number of functions for you to use and interact with the game world (see the Script Functions page for a detailed list).
MessageBox, "Welcome to scripting..." ; Display a message to the player if ( player->GetPos, X > 100 ) ; Check the player's position if ( "achel"->GetSpell, "ash woe blight" == 1 ) ; Check for a disease on an NPC
For exact use and description of the functions, see the Construction Set help file, the above Functions page, or look at some of the many scripts in the game. Note that sometimes the editor's help file is not exactly correct. Also note that most functions do not accept variables as input, for example:
float XPos set XPos to -10 AiTravel, -10, 1000, 501, 0 ; Literal values work fine AiTravel, XPos, 1000, 501, 0 ; Does NOT work as AiTravel does not accept variables SetPos, X, XPos ; Works because the SetPos function accepts variables (Tribunal required)
See the description of the various functions to learn whether they accept variables or not (most functions do not).
Using the proper formatting when you create scripts is important to ensure the correct operation of the script. Unfortunately Morrowind's script engine can be quite picky about things like lack of spaces (or other such things) which will result in an error. These errors can be quite frustrating to solve because the error makes it appear that your script is the problem. Use the code sample and list below for things you should watch out for.
if ( SomeValue == 1 ) ; 1, use spaces after/before brackets in if statements AiActivate, ObjectID, 0 ; 2, use commas when calling functions endif "urzul gra-agum"->Enable ; 3, use quotes to surround object IDs containing spaces
- Put spaces before and after the brackets in if statements. You will occasionally get an error message if you do not do this. Also use spaces surrounding the ==, <, >, ... operator in the if statement.
- Always use commas when calling functions (both after the function name and after each parameter, except the last). Morrowind allows you to call functions without commas, but it is a good habit to use them anyways.
- Wherever you use object IDs that have spaces in them, use double-quotes to surround the ID.
- Never put spaces around the fix ('->') unless the ID before it is surrounded by quotes (see Scripting Pitfalls)
This is one of the things which makes scripting in Morrowind so confusing to use, especially for those familiar to real programming languages.
Putting It All Together
That is the basic explanation of creating scripts in Morrowind, though the best way to learn is from practice. The following script is a relatively short and simple one. When put onto a container it will only open if the player has a specific item in their inventory. If not, the container will release a short trap depending on the player's level. Much more complexity can be added to the script if you wanted it.
begin TrickChestScript short OpenAttempt ; Check for the player attempting to open the chest. Use the OpenAttempt variable as ; putting all the code into the OnActivate if statement becomes difficult for more ; complex scripts. if ( OnActivate == 1 ) set OpenAttempt to 1 end if ; Stop the script if not trying to open the chest if ( OpenAttempt == 0 ) return endif ; Check the player for the 'secret' key, which is 10 pillows if ( player->GetItemCount, "misc_uni_pillow_01" >= 10 ) Activate ; Open the container normally Set OpenAttempt to 0 MessageBox, "The chest opens mysteriously..." ; The player doesn't have 10 pillows, activate trap else PlaySound3D, "Heavy Armor Hit" ; Play ominous sound Set OpenAttempt to 0 ; Cast trap on player depending on level if ( player->GetLevel < 10 ) Cast, "poisontrap_10", Player elseif ( player->GetLevel < 25 ) Cast, "poisontrap_50", Player elseif ( player->GetLevel < 50 ) Cast, "poisontrap_150", Player else Cast, "poisontrap_250", Player endif endif end