Scripting for Dummies

From Morrowind Modding Wiki
Jump to: navigation, search

[WIP]Wiki Page
This page is still under construction. You can help to finish it by clicking on the "Edit" button to the top-right of the page when you're signed in.

Page is missing vital info.


The first eight editions of Morrowind Scripting for Dummies was written by Ghan Buri Ghan. The ninth edition was written by Yacoby and melian; with help and information from the community. It has now been reproduced on this wiki with permission from Yacoby.

The original PDF and Word files are available for download at these links.

Foreword to the Ninth Edition

Another update, this time by more than one person, now you can get really confused when it mentions that “I” did something or found something *grins*. Although this update was intended as a bugfix, a huge amount of new information has been added - since much of it concerns the bugs and pitfalls of the scripting language that have been discovered since version 8, you will find changes and additions scattered throughout.

I have also added a couple more functions that were missing from the last version, and there is a small section on script extenders to give a brief overview of what can be done with each which should hopefully prove informative to anyone wanting to know more about what they do. Galsiah proveded a large section on the failings of get/mod/set stat; there is a lot more info on dialog and how it works, which should be very useful; a short explanation of float values and how they work has been added; some sections have been rearranged for clarity; and I think I've removed all the en dashes and typographer's quotes from code blocks, so it should be safe to copy-paste most of the example scripts now.

Special thanks go to GhanBuriGhan, who gave me permission to update his indispensable guide. Also thanks to Dave Humphrey for his permission to use the information on UESP wiki, and everyone who contributed to that wiki. I would like to thank exclusiveor77, DinkumThinkum, Björn, abot, Galsiah, Casey Tucker, cyran0 and JOG for the huge amount of information they gave me on the forums.

However without the help of the rest of community, everyone who collected all the information, posted their experiences, this guide would have never happened.

Yacoby and melian

Foreword to the Eight Edition

Oh no, MSFD updates again! Quite a few changes this time. I have continued to reorder functions to achieve a more consistent classification. In the course of this restructuring of the text, Tribunal and Bloodmoon functions are now also sorted among the rest of the functions (but clearly marked), and the same applies for Get/Set/ModStat type functions - the ones that affect magic are now in the Magic section, the ones that affect combat are in the combat chapter, and so on. I reformatted all the function entries for better visibility. There are also a good number of corrections and additions: check out the corrected list of idles in AIWander, more detailed info on functions such as OnDeath, OnActivate, or the revelation that some functions, like PositionCell use a different unit for the angle setting (minutes instead of degrees). There is a lot of interesting new detail on the intricacies of RemoveItem, OnPCEquip, and SkipEquip. Throughout, I have added a couple more sample scripts. I have assembled a tips list on Dialogue from the information from a once-pinned thread on the official forums. To make your mod-testing easier, I now have a much more complete list of console commands, and you may find that some of these may actually be useful script functions as well. A nice collection of new tips and tricks have also been added, e.g. on how to detect savegame loading, alternative script editors, a fast sine and cosine calculator and more.

I would like to mention something here, that has become more and more evident in recent discussions. Apparently the different versions of the game (Morrowind, the expansions, various updates, European/American versions), have quite a number of functional differences with respect to scripting. This may explain a lot of the conflicting information I get about some functions. Some functions actually seem to have lost functionality in later versions of the game, some regained functionality, etc. In the absence of a full change log by Bethesda it is unfortunately impossible to really give a final verdict in many of these cases. Again I can only stress that this guide would never have become what it is without the help and support of the modding community. This time, I would like to say special thanks to Nigedo, DinkumThinkum, ManaUser, ThePal, Erstam, JDGBOLT, Klinn, and MentalElf, for their tireless help in finding and solving many errors or inconsistencies in the last edition, or sharing scripts and information with me. Thanks also to Emma, for all the info she put into that dialogue thread on the forums.

GhanBuriGhan

Disclaimer & Copyright Statment: The Elder Scrolls, Morrowind, Daggerfall, Arena, Tribunal, Bloodmoon the TES Construction Set etc. are property of Bethesda Softworks, a ZeniMax Media company. The authors and editors of this manual make no claims to these names and trademarks. In all other respects I (GhanBuriGhan) maintain the copyright for this document. This guide is fully unofficial and in no way am I, any other authors or editors, or Bethesda responsible for any damages or loss of data, patience, hair, or sanity inflicted through the use of this manual. You can freely distribute this document as long as you keep it intact and unmodified.

If you find older versions of this document on the web for download, please inform the webmaster about the new version.

Introduction

Using this Guide

If you are new to scripting, you should probably start by reading this introduction and especially do the tutorial. It is my best attempt at explaining scripts, what they do, and how to program one, in simple terms.

Secondly this is a manual, a reference, a handbook. The bulk of this document is a documentation of the available functions. This part is not written for the beginner, you are expected to know the basics of scripting already. I have however tried to provide a lot more info, explanations and sample scripts than the original helpfile, to make it easier to use these functions correctly in your scripts. Use the index at the end of the document to find info on a specific function, or the table of contents to find functions related to a general area of interest. If you run into errors, the Troubleshooting section may help you a bit. In the Appendix you find lists that may also come in handy as a reference.

Thirdly, as an advanced scripter you may find the Tips and Tricks section interesting that covers both basic advice and advanced scripting techniques.

Finally a word of advice: don't take what's written here as gospel. The info in this guide represents the best of my knowledge and forum wisdom, but that doesn't mean that there aren't mistakes and oversights, etc. E.g. if I write that a function doesn't take variables as arguments it probably doesn't – but if it is important for your mod, by all means try anyway. It may have changed with a patch or maybe simply nobody checked before. So run a test, and if you find something new, let me know, and I will add the info in a possible future update.

What is a Script

Scripts are basically pieces of code written in a special scripting language (I will call it TES script from here on). These little "programs" will run during the game and can perform certain things in the game, lots of things actually: trigger events, control time and place, make things and creatures vanish, appear or move, give messages to the player, change stats, even change the weather – the possibilities are great.

TES Script is a unique scripting language, it is not used outside the TES Construction Set. As a scripting language it has certain limitations compared to a "real" programming language, like, e.g. C++:

  1. Limited scope - Don’t expect to be able to program anything that is not already in the game in one way or another – which is not to say you can not achieve new and unusual things with scripting! But you can’t use TES script to, say, program a word processor.
  2. Not an SDK (software development kit) - TES script will not let you actually work with and change (parts of) the games source code. That’s why you can’t use TES script to, for example, add new weather-effects. Those are hardcoded and you would need to change the actual game .exe to do that.
  3. Interpreted – The code needs a separate program (in this case Morrowind) to run; unlike compiled code that could be run by itself, like an .exe application.

What can Scripts do

Scripts for Morrowind are a way to have the game dynamically react to what the player does in the game world. You can use scripts to manage complex quests. You can use scripts to create custom items that perform actions beyond what regular enchantments could. You can use scripts to create traps. You can use scripts to change NPC or creature behavior. Remember the character creation in Morrowind? It’s basically controlled by a number of scripts. Seen Fargoth sneak around to his stash in Seyda Neen? That’s a script directing his moves. Freed any slaves? That also is handled by a script. So the short answer to the question is: a lot.

What Scripts can't do

The TES script language is limited in its capabilities – there are only so many functions that you can use and sometimes the possible uses are not all that you may wish them to be. In fact some functions are buggy or plain broken. In many cases smart scripters can find workarounds for apparent limitations, but don't expect miracles. Many things are hardcoded and can not or only indirectly be influenced by scripts.

Scripting Tutorial

If you are completely new to scripting and programming in general, the thought of using TES script might be a little daunting – I have therefore written an extended tutorial that will walk you through making your first script. I will also explain the main elements of the scripting language as we go. There will be other explanations on the way, but the key instructions will be in bold print.

We start by opening the script editor: Start up the TES Construction Set, open the Morrowind.esm file and then select Edit Scripts from the Gameplay menu to open the scripting window.

The Scripting Window

You enter the script editor either by selecting Gameplay – Edit Scripts from the menu, by clicking the edit script button (the pencil) in the taskbar or by accessing it from an Object or NPC dialogue, by clicking the button with the ellipses […] next to the script field. The editor window is pretty basic:

SFD ScriptWindow.png

Let's have a look at the buttons in the taskbar, from left to right: Open lets you select a script to edit. Save error checks the current script and compiles it or gives out error messages – note, however, that the plugin and thus the script is not really saved to disk at this time. When programming large scripts frequently use the save command in the main TESCS window after you have saved the script here, just in case the TESCS crashes.

Note that if you edit the script and suddenly hit "save plugin" to backup in the middle of the work, your updated script will NOT be saved with it. You must save it manually first. Also, if you just close script window, it doesn’t mean that script will be saved. You must take care of it yourself (Thanks to Kir for this tip).

Forward and Backward arrows jump to the next or previous script, respectively (alphabetical order). If you give your scripts a common tag, that will make it easier to jump between the different scripts of your project, e.g. start every script name with AA_Scriptname this will put them right at the beginning of the list and keep them neatly together. Compile all recompiles all scripts (what's this good for? I don't really know). Finally, the delete button deletes a script and the last "arrow down" button closes the script window.

The help menu gives quick access to the function and command pages of the helpfile (of moderate utility, hence the creation of this manual!)

You can cut, copy, and paste from and into the editor window by using the Windows standards ctrl-c for copy, ctrl-x for cut and ctrl-v for paste.

What do we want

Before we really start writing our tutorial script we should decide what we want it to do. For this tutorial I decided we are going to make a Riddle Chest: The chest will ask a riddle and only the right answer will open the chest. If the player provides the wrong answer, a trap will go off, hurting the player, and the chest can't be opened. That’s a fairly complex undertaking, but we will take it step by step.

Writing a script

Ok, once you've got the Edit Script window open, click into the main part of the window. That is where you will write your script.

Naming a script: Begin and End

First of all we must give our script a name – every script must start with the declaration of this name. So please type:

Begin my_first_script

into the editor window. Note the underscores: Your name should be one word. Also note that the script language is not case sensitive, so Begin could also be written without the capital letter: begin. This name is the handle by which the script will be known in the TESCS. Try hitting the save button now: you will get an error message about "you need to end your script with end scriptname. So, for the editor to recognize the script we also need to indicate an end: next, write:

End

in a new line below the above. As you see we can omit putting the name of the script in this line again, just end will do. When you hit safe now, you will see the name appear in the title bar of the script editor, indicating that the script has been accepted. This is the shortest script possible - and of course it doesn’t do anything at all.

Detecting an action by the player

Next, we need a way to determine whether the player tries to open the chest. In TES Script we distinguish between objects, functions, and commands:

  • Objects - Things in the game world, be they visible objects, creatures, NPCs or just sounds.
  • Functions - The "words" of TES Script that let us either manipulate these objects or let us gather information about them.
  • Commands - "Words" that structure the scripting language, but do not operate on any Game objects – an example is the word "Begin" we used to tell the script editor about the name of our script.

To tell the game which object it is supposed to perform a given function on we can use the arrow, or fix : -> (really just a hyphen (-) and a greater-than sign (>)). You specify the object for the function on the left (we also call this the calling object) and the function to be performed on the right:

Object_ID->function, [parameters]

A function may or may not have parameters. Parameters could be other object ID's, numbers and in some cases, variables.

What we need for our riddle chest is the OnActivate function: this is an informative function that tells us whether the player has "activated" an object in the game world or not. This function returns a value of 1 (which means "true" in programming terms) if the object has been activated, that means the player targets it and presses the "use" button (space, by default). So what we need to do is check if OnActivate becomes "true" anytime in the game. So edit your script to look like this:

Begin my_first_script

If ( OnActivate == 1 )

; here we will enter what happens when the chest is opened

endif

End

A couple more things need to be explained here: The "if" command is there to check a condition – whenever the expression in the parentheses is "true" the following lines of code will be executed until the "endif" command is encountered. The "==" checks if an expression (in our case the "OnActivate" function) on the left of it is equal to the expression on the right of it (in our case to 1). If you forget the endif command after an if command, the editor will complain with an error message. The ";" semicolon denotes a comment – whatever you write behind the semicolon will be ignored when the script is run. If you ever write larger scripts you should learn to love this possibility.

Writing Text and Obtaining Decisions from the Player

Now we want our trapped chest to ask the player a riddle. For this we use the MessageBox function that allows us to display some text on the screen and also to display choices that the player can select from. Unfortunately Morrowind has no option to have the player type in the answer to our riddle, so we will have to give multiple choices. The line for that could read:

MessageBox "Voiceless it cries, wingless flutters, toothless bites, mouthless mutters. What is it?", "Bat", "Old woman", "Wind", "Wraith"

The first text is the text actually displayed in the box, the other texts, separated by commas tell the game to make "buttons", with the text given displayed.

But how do we ensure that the riddle is asked only the first time we try to open the chest and not every time? We now come to a very central point: the use of do-once conditions and state variables. Most of the problems that beginners encounter with scripting for Morrowind have their roots in misunderstanding how the scripts are actually executed and how scripts should accordingly be structured. So let's have a look at this.

How local scripts are executed

Every script that is attached to an Object or an NPC (local script) is executed every frame the game displays on screen while the cell with the object is active (indoors only the cell the NPC is currently in is active, outdoors the PC’s cell and all adjacent cells are active). So the complete script (not just one line of it) is executed 10-60 times a second or however fast your computer runs the game! It is best to imagine every local script wrapped in a big “while loop”:

while (Object is in active Cell)

[Your script code]

endwhile

This is the reason why the following script spits out a continuous stream of messages (if attached to an Object or NPC in the same cell as the player). Try it, if you want:

Begin Message_script

MessageBox "Thousands of useless Messages"

End Message_script

This example is relatively harmless, but imagine what happens if you would use a line of code that adds an item to the players inventory, or places a monster next to him, etc.! For this reason, “Do Once” constructions are very essential and something you will probably use a lot while scripting for Morrowind. So, let's go on with our tutorial script: we need to declare a variable and use it to make sure the message is only displayed once. Change the script to the following:

Begin my_first_script

Short controlvar

If ( OnActivate == 1 )
    If ( controlvar == 0)
        MessageBox "Voiceless it cries, wingless flutters, toothless bites, mouthless
mutters. What is it?", "Bat", "Old woman", "Wind", "Wraith"
        Set controlvar to 1
    endif
endif

End

(Please note that the MessageBox command should be in one line in the editor!)

"Short controlvar" declares a new variable I called "controlvar", of type short. For the moment it's enough to know that this is variable that will contain integers (whole positive or negative numbers). A variable is a "placeholder" that can take on different values. The if command we already know, the set command is new, but simple enough – it sets our variable that had the value 0 before (all variables start out at zero when declared) to 1. This, in connection with the if ( controlvar == 0 ) command provides a do-once condition – the next frame the script is executed after the variable was set to 1 the if condition will be false and the message box will not be displayed again.

Now our script is already capable of being run, so lets test it:

  1. Save the script and close the script editor window.
  2. Go to the TES construction set, Object window, select the container tab and open "chest_small_01".
  3. Change the ID of the chest to "tutorial_chest"
  4. In the script dropdown field, select my_first_script
  5. Save the object as a new object, save the mod, and quit the construction set. Start Morrowind and load a savegame.
  6. Now bring down the console (usually the ~ key, or whatever you have to the left of the "1" on the main keyboard) and in the console window, type: PlaceAtPC tutorial_chest 1,1,1 and hit return.

Take a step back (err, let your player character step back, that is!); you should now have a little chest sitting on the floor right in front of you. Clicking on it should bring up our message, which should look like this:

TODO: Add screenshot

Clicking on those buttons will just close the messagebox for the moment, and clicking on the chest again, nothing should happen either – which is good, it means our do-once condition works.

Ok, leave Morrowind and go back to the editor, and load your plugin again.

We now need to figure out which answer the player selects, and script appropriate reactions for right and wrong answers. The function to test the selected answer is "GetButtonPressed". This function returns a number depending on which of the buttons of a message box has been clicked on with the mouse. It will return "0" for the first button ("bat" in our example) and 1, 2, 3 etc. for the following buttons, in the order you listed them in the messagebox function. While no answer has been selected, the function will return –1, so we have to take care of that, too.

The "Activate" function will make our chest open, in fact activate will simply trigger the standard action that would usually be performed when you "use" the selected object – e.g. doors would swing open, NPCs would initiate dialogue etc. The following update to our script also demonstrates how you can use a control variable to force MW to process functions one after the other, although the complete script is processed every frame of the game: simply increment the control variable and test it in a series of if – elseif statements. This is a very safe way of scripting for MW – it may not always be necessary, but it's safe.

Please edit the script to the following:

Begin my_first_script

Short controlvar

Short button

If ( OnActivate == 1 )
If ( controlvar == 0)
    MessageBox "Voiceless it cries, wingless flutters, toothless bites, mouthless mutters.
What is it?", "Bat", "Old woman", "Wind", "Wraith"
    Set controlvar to 1
elseif controlvar > 1
    activate
endif
endif

if (controlvar == 1)
    set button to GetButtonPressed
    if ( button == -1 )
        return
    elseif ( button == 2)
        MessageBox "The answer was correct"
        Activate
        set controlvar to 2
    else
        MessageBox "The answer was wrong"
        set controlvar to -1
    endif
endif

End

Take a look at the part that starts with "if (controlvar == 1)". We have set controlvar to 1 as soon as the chest got activated. Now we test for which button is being pressed. We do this by assigning the new variable "button" a value returned by GetButtonPressed. Since the script is still running, even while the game seemingly pauses to await your decision, we first test if no button has been selected yet – return tells the game engine to stop processing the script for this frame.

Our correct answer was "wind" which corresponds to button number two – if button number two gets pressed, we will tell the player that he gave the right answer, and "activate" will open the chests inventory in the usual way. All other values of button mean that the player has selected a wrong answer, so we can use the "else" command here. In this case we tell the player what a fool he was and the chest is not activated.

Now look at the little addition at the top of the script:

If ( OnActivate == 1 )
If ( controlvar == 0)
    MessageBox "Voiceless it cries, wingless flutters, toothless bites, mouthless mutters.
What is it?", "Bat", "Old woman", "Wind", "Wraith"
    Set controlvar to 1
elseif controlvar > 1
    activate
endif
endif

This means that, whenever the chest is activated in the future, it will only open if controlvar is greater than 1. Check above: when the player provides the wrong answer in the riddle, controlvar is set to –1, so he will never be able to open the chest. But if he knew the right answer, controlvar is set to 2, and from now on the player can open the chest as often as he likes. Save and run your plugin, and test as described above.

Your first bug

Now, you will probably have noticed that the script does almost – but not quite – what we wanted. After clicking the correct answer, the chests inventory doesn’t open as intended. Now, the logic above seemed fine, so what is wrong? Let's try the following (change the corresponding part of your script according to the fragment below):

if (controlvar == 1)
    set button to GetButtonPressed
    if ( button == -1 )
        return
    elseif ( button == 2 )
        MessageBox "The answer was correct"
        set controlvar to 2
    else
        MessageBox "The answer was wrong"
        set controlvar to -1
    endif
elseif ( controlvar == 2 )
    Activate
endif

See how I moved the activate command to the section that tests for controlvar == 2? This provides a cleaner sequence of events, and as I mentioned above, this can be very important when scripting for Morrowind – always try to avoid doing too many things at once! Well, run and test it.

Great, now the inventory opens as we wanted, but what is this? The cursor is real slow, and we can't close the inventory! Look above – controlvar was set to two, and remains there, we do not change it again – therefore the game now gets continuous "Activate" commands each time the script is processed (every frame)! That’s why we can't close the inventory – it gets reopened immediately. So change the following part of the script:

elseif ( controlvar == 2 )
    Activate
    Set controlvar to 3
endif

Test again: now everything works the way we wanted. I hope I have not confused you with the above excursion into the process of debugging, but it is a very important thing to know about – you will constantly have to rethink your scripts and try different ways of doing it to be successful.

What is still missing? The trap effect of course!

Putting a spell on the player

Our chest will put a curse on the player if he fails to answer the riddle. First go to the spellmaking tab in the editor, right click and select "new". Give the spell the ID "Frost_Curse", name it "Frost Curse" and make it type "curse" then give it a magnitude of e.g. 1-5. It should look like in the picture below.

SFD SpellMaking.png

Now, we need to put this curse on the player. For this we use the AddSpell function. After some time we will remove the curse again using the RemoveSpell function, for this we need to make a timer. Edit your script again:

Begin my_first_script

Short controlvar
Short button
Float timer

If ( OnActivate == 1 )
If ( controlvar == 0)
    MessageBox "Voiceless it cries, wingless flutters, toothless bites, mouthless mutters.
What is it?", "Bat", "Old woman", "Wind", "Wraith"
    Set controlvar to 1
elseif controlvar > 1
    activate
endif
endif

if (controlvar == 1)
    set button to GetButtonPressed
    if ( button == -1 )
        return
    elseif ( button == 2)
        MessageBox "The answer was correct"
        Activate
        set controlvar to 2
    else
        MessageBox "The answer was wrong"
        Player->AddSpell, "Frost_Curse"
        set controlvar to -1
    Endif
elseif ( controlvar == 2 )
    Activate
    Set controlvar to 3
elseif ( controlvar == -1 )
    Set timer to ( timer + GetSecondsPassed )
    if timer > 10
        Player->RemoveSpell, "Frost_Curse"
        set controlvar to -2
    endif
endif

End

Let's go over this. Player->AddSpell, "Frost_Curse" puts the curse we created earlier on the player. Note how we need to use "Player->" to make sure the effect really targets the player. Otherwise we would curse the chest (which is the default object, because the script is attached to it), which wouldn’t make a lot of sense…

This bit:

Float timer
Set timer to ( timer + GetSecondsPassed )
    if timer > 10

…that is how you make a timer in Morrowind. GetSecondsPassed is a function that returns the time in seconds that has passed since the last frame. Since this is usually a fraction of a second (as the script gets called every frame), it is only natural that we need a float variable for this purpose – a variable that can store numbers with decimals. So when the timer has been running for 10 seconds we remove the curse again, and make sure we do this only once:

        Player->RemoveSpell, "Frost_Curse"
        set controlvar to -2

Ok, save and test your mod. Works fine now, doesn’t it? Well, almost. Try the following: let yourself be cursed, and then open your inventory. Wait. See how the curse terminates after some time, without hurting you? Of course: the script is still running, but spell effects are only calculated while in game, not while you are in the menu. We don’t want the player to get off the hook so easily, so we need to put something in our script that stops it from processing when we are in menu mode. Luckily there is the MenuMode function, which returns 1 when you enter the menu. So we can put this at the start of our script:

If ( MenuMode == 1 )
    Return
Endif

Remember, return tells the game to stop processing the script for this frame. Ok, now we have our final working script. Congratulations! If you want, experiment a little more with this script: Put the chest into a location in the game and lock it. Then try unlocking it (Unlock function) with the script in addition to activating it. Try adding a sound, when the riddle is successfully solved (e.g. PlaySound3D, "skillraise"). Try using the "Cast" function instead of "AddSpell". Previous users of the Tutorial have spotted a potential bug in it: what happens if the player leaves the area with the scripted chest before the curse is removed again? How would you fix this?

This is the final script:

Begin my_first_script

Short controlvar
Short button
Float timer

If ( MenuMode == 1 )
    Return
Endif

If ( OnActivate == 1 )
If ( controlvar == 0 )
    MessageBox "Voiceless it cries, wingless flutters, toothless bites, mouthless mutters.
What is it?", "Bat", "Old woman", "Wind", "Wraith"
    Set controlvar to 1
elseif ( controlvar > 1 )
    activate
endif
endif

if ( controlvar == 1 )
    set button to GetButtonPressed
    if ( button == -1 )
        return
    elseif ( button == 2 )
        MessageBox "The answer was correct"
        Activate
        set controlvar to 2
    else
        MessageBox "The answer was wrong"
        Player->AddSpell, "Frost_Curse"
        set controlvar to -1
    Endif
elseif ( controlvar == 2 )
    Activate
    Set controlvar to 3
elseif ( controlvar == -1 )
    Set timer to ( timer + GetSecondsPassed )
    if timer > 10
        Player->RemoveSpell, "Frost_Curse"
        set controlvar to -2
    endif
endif

End

How to Learn More

After this tutorial you may ask yourself how to continue with learning how to script. A good way is to look at the example scripts in this guide or at scripts that are already in the game (either form Bethesda or from mods). Try to find a script that is similar to what you want to do and then copy the script and change it to fit your needs. Read the general information below and the descriptions of the functions you may need to do what you have planned. The ordering of the functions into thematic groups should help you to find the right ones. Finally, the official forums are a great place to find information (use the search function) or to get help on a specific problem. The rest is practicing, practicing, practicing.

Types of Scripts

Local Scripts

Any script that is running on an object or Actor in the game (assigned in the script-dropdown field of the object or Actors object window) is a local script. Local scripts are only active if the cell is loaded – this is the current interior cell, or the current and all directly neighboring exterior cells. When the object is outside of this range the script is not running, but the local variables are saved. You cannot stop a local script using Stopscript.

Global scripts

Any script that is not attached to any object is a global script, and is by default not executed until you call it (see below). Note that there is no default object for a global script to work on, so objects must always be specified: while the following will work in a local script attached to an NPC:

AITravel 1150, 8899, 1110

You will have to specify the NPC in a global script:

"NPC_ID"->AITravel 1150, 8899, 1110 ;NPC_ID is the ID, the unique identifier for each object in the editor

Global scripts are active all the time once they have been activated and until they are specifically terminated. Thus, once activated, they will be processed every frame as described for locals scripts above. That is why they should be used with caution, as too many, or too complicated global scripts can easily slow the game down a lot.

The command to start a non-active script is:

StartScript, "Script ID"

With Tribunal and Bloodmoon you also have the option to make a script start automatically with the game. In the TESCS under the menu Gameplay/Edit starting Scripts/ you can add any script to the list of automatically started scripts.

The function to terminate a global script is:

StopScript, "Script ID"

Variables local to a global script will be saved temporarily when the script is stopped and restarted. In order to ensure that variables are always saved, the script must be run at least once every load session. If you need to be sure the variables are reset, you should reset them yourself: don't rely on it happening automatically.

A simple way to ensure variables are saved is to use a startscript (only available with expansions); so for example the startscript might look something like this:

begin KeepVarsScript

if ( ScriptRunning, MyScript == 0 )
    set MyScript.KeepVars to 1
    StartScript MyScript
endif

StopScript KeepVarsScript
end

…and the script "MyScript" might look like this:

begin MyScript

short KeepVars

if ( KeepVars == 1 )
    set KeepVars to 0
    StopScript MyScript
    return
endif

;main body of script goes here

end

It is possible to use the StartScript function to run global scripts that are tied to an object or Actor. These are called "targeted scripts".

"Object_ID"->StartScript "Script_ ID"

These scripts resemble both local scripts (in that the functions called always default to the object or Actor the script targets) and global scripts (in that they are always running and can be terminated with StopScript).

Note: read more on the special case of "targeted scripts" in the Tips and Tricks section.

Syntax

There are some unique features in TES script that should be explained before we go into more detail.

Beginning and ending scripts

Begin Script_ID End End Script_ID

Every script must have the Begin and End tags. The specified name will also be the ID you will reference the script by (be it from other scripts or inside the TES CS object windows). A script may start out with comments, but the first line of real code must be "Begin xxxxxxxxx". As with other objects, it is recommended that you give your scripts a unique tag. I usually use GBG_Scriptname. This ensures that your scripts are easily identified, they all are neatly listed in one block in the scripts list, and there is little danger of conflicts with other mods using the same name. Using leading underscores e.g. _Scriptname is not recommended.

If you want your scripts to appear at the top of the list of scripts, put a 1 in front, for example, 1YAC_ScriptName

General syntax for functions

Functions are not case sensitive, but using the case sensitive form as suggested by Bethesda (e.g. GetSpellEffects instead of getspelleffects) makes scripts easier to read, so it’s recommended to keep it that way.

"Object_ID"->Function, [parameters]

This is the format for all functions that act on or refer to a specifiable object in the game world. The "arrow" or "fix" designates which object a function will be performed on. The "object_ID" is the unique ID that is given to each object in the editor (usually the first field in any object window). You need this ID, not the Name! If you call a function without a designated Object-ID, the function will be performed on the default object, which is the object the script is attached to.

This is not to say that there might not be another object referenced as a parameter:

"Object_ID1"->Function, "Object_ID2"

Notes:

Functions with a "fix" will only compile if the object has already been placed into the game world in the editor (with at least one reference), otherwise the script will not compile. If you compile the script and then delete all references, the game will give errors on load and CTD.

More than one function may be used in a set expression, but the functions always apply to the same reference. For example:

set SomeVar to ( player->GetStrength ) + ( player->GetEndurance )

and

set SomeVar to ( player->GetStrength ) + ( GetEndurance )

will both have the same effect, and specifying different references will not work.

Referencing non-unique items with the "fix" will usually perform the function only on the first reference of the object! So using this in a global script:

"cliff racer"->ModCurrentHealth -1000

will not have the desired effect, it will only kill one of the annoying bastards. However attaching a script to the creature with just

ModCurrentHealth -1000

Would do the trick, because every reference of the cliff racer will have the script running, and apply the function to itself. Note that this does not apply to all functions (e.g. SetHealth in the above example would affect all references the player has not yet encountered).

A number of functions refer only to the player or not to an object at all, and are therefore using the fix is meaningless or may produce errors. E.g.:

If ( GetPCRank == 0 ) If ( CellChanged == 0 ) FadeOut, 2

General syntax: Commas, parentheses and spaces

TES Script is not too picky about syntax. Case mostly doesn’t matter, commas can be left out, and spaces are mostly ignored. Nevertheless I would advise adhering to the following principles:

  • Either avoid commas, or always use commas: inconsistent usage can cause problems.
  • If the ID contains a space or begins with an underscore, you must use quotation marks: "Object ID" or "_Object_ID". Better to avoid spaces altogether: Object_ID
  • Get used to always leaving spaces around parentheses and operators, sometimes it seems to cause problems if you don't: if ( variable == 1 ), not: if (variable==1). While this doesn’t matter most of the time it generates weird and almost untraceable errors sometimes, so you are much better off always leaving a space.
  • The fix (->) is a little more complicated. If IDs are contained in quotes, you should not leave spaces around the fix:
    "Sirollus Saccus"->GetItemCount netch_leather_greaves
    The above will work, but spaces around the fix would cause problems. (Thanks to Simpleton and DinkumThinkum for this info.) However, since it has also been reported that a lack of quotation marks can cause problems in combination with spaces round the fix, I'm going to recommend that you don't leave spaces round the fix at all (should be fine in all cases).

Comments

Comments are marked by a semicolon ;. Comments can be added in their own lines or behind lines with code:

; enter sneak mode
Fargoth->ForceSneak
Fargoth->AiTravel -11468.595,-71511.531,173.728 ;goes to tree

Indentation/using Tabstops

For your own sake, use proper indentation (tabstops) for if-elseif constructs – makes it much easier to keep track of them, so you don’t forget an endif at the end. In the tips and tricks section you will find a link to an external EMACS editor mode for TES script that will do automatic indentation.

if ( variable1 )
    if ( variable2 )
        [do something]
    endif
endif

is better than

if ( variable1 )
if ( variable2 )
[do something]
endif
endif

Variables

Types of Variables

There are three types of variables in the TES script language: short, long and float. According to the manual these cover the following data ranges:

  • Short: -32,768 to 32,767 (signed integer)
  • Long: -2,147,483,648 to 2,147,483,647 ( long signed integer)
  • Float: 3.4E +/- 38 (float, 7 digits)

Apparently the boundaries for Long given here are only partly correct, in the TES-CS you can assign a maximum value of 2147483520 (Forum info / Argent). Theoretically there should be string variables too, but to my knowledge these are not implemented. Unfortunately there are also no data types to store Object_Id's, which limits the power of the scripting language to a certain extent. Variables can be grouped into local variables (valid only in the script that declares them) and global variables (valid in every script).

Note: A long global is effectively a float! In a script, local to that script a long will have a full 32 bits of precision. But used as a global, the number of bits of precision drops to 24, as when it exists globally a long is a float. Mental Elf discovered this when attempting to use "bit packing" to get 32 flags into a global long (forum info / mental elf).

A Note about Floats: Floating point numbers in a computer are stored in a format similar to that used for scientific or engineering notation. The number is rounded off to a fixed number of significant digits, any leading or trailing zeros are dropped, and an exponent is added to indicate the correct location of the decimal or binary point.

The number 2385901045 would be stored similar to this: 2.385901x109 In fitting the number into a fixed number of bytes, we have lost the end 3 digits, so the number is no longer quite what you set it to.

If you set a float to a small number, like 5.5, it would still be stored as 5.5, because as it doesn’t take up much room, no numbers need to be cut off.

The advantage of this format is that a wide range of values can be expressed using a relatively small number of digits or bytes. The disadvantage is that the rounding off means floating point numbers don't have the precision of an integer, so they shouldn't be used when you need to make exact comparisons or keep track of exact counts.

For example, a floating point variable is fine if you just need to check if a distance is greater or less than a certain value. But if you need to check if the player has specific number of some item in their inventory, then you want to use a long or short integer variable for the count. (Forum info / DinkumThinkum)

An example by BungaDunga shows that when doing math with floats, the answer isn’t always what you expect:

Float num1
Float num2
Float num3

Set num1 to ( 1 / 3 )
;num1 is now 0.3333333

Set num2 to 3
;num2 is now 3

set num3 to num1 * num2
;num3 is now 0.9999999, rather than what you would expect, 1.

if ( num3 == 1 )
    ; Never happens.
endif

A lot of functions return float values (e.g. GetDistance, GetScale, GetSecondsPassed…): the same thing applies. Test a range, not an exact value! E.g.:

if ( ( GetPos x ) == 500 )
    ;will be false if it's 499.9999, or 500.0001, etc
endif

but this will work:

if ( ( GetPos x ) > 499.5 )
    if ( ( GetPos x ) < 500.5 )
        ;it's close enough for me
    endif
endif

Another common mistake is to check if the GameHour is an exact number. As the Gamehour variable is increased every frame, and every frame is a slightly different length, the Gamehour variable soon ends up with a value like this, 10.12853. So never test if the GameHour is exactly equal to a value, as it is very unlikely to happen, test if it is greater than or equal to a value.

Local variables

Local variables these have to be declared in the script:

Float floatvarname
Short shortvarname
Long longvarname

Local variables are unique to a specific instance of a local script. This means that the local variables in multiple objects with the same script do not influence each other. The names you use for variables are pretty much up to you as long as they start with a letter, but you have to avoid using function names (this will result in errors during runtime) and reserved characters (e.g. - + / * = " )( etc.) which will result in compiler errors. E.g. "variable-1" will not work as a variable name. Underscores as in "my_variable" are ok, but avoid leading underscores. A dot has a reserved meaning as well (see "Referencing variables in other local scripts" below).

Global variables

To declare a global variable go to the Gameplay menu and select Globals. Right click for "new", name it, and set the type and starting value for the new global variable, if one is needed. By default it will be 0. Global variables are very useful for involved quests when you need to keep track of things over an extended time and space. They are also a simple way to share information between different scripts.

Note: if you declare a local variable with the same name as a global variable, the global variable will become invisible for this script. Do NOT declare a global variable as a local!

Referencing variables on other objects and scripts

Set … to

If a unique object has a local script running on it you can change variables from outside the script in the following way:

Set MyObject.variable to 100

or

Set MyObject.variable to local_variable

This method changes a local variable in the object’s script. The object must have a script on it for this to be valid. The object does not have to be in an active cell when setting the variable, but it will only work if the cell containing the target object/(script) has previously loaded - (Cyran0). Note: The scripting system looks at the first object in the database, thus you should only reference objects that are unique (exist only once).

Note that the reverse does not work:

Set local_variable to MyObject.variable ;this doesn't work!

Use a global variable to transfer information in this way, or set local_variable from the other script using the syntax above.

if ( anotherobject.x > 0 )

apparently works.

Furthermore I realized only recently that this syntax also works for global scripts:

set Global_script_name.variable to 1

This is useful to avoid using more global variables then necessary or also as a console command to debug global scripts.

Note: If your object or global script starts with a number, you need to put quotes around it.

"11NPC01".RemoteVar ;---Good
"11NPC01.RemoteVar" ;---BAD
11NPC01.RemoteVar ;---BAD

A caution when setting variables from outside the script (DinkumThinkum):

If you recompile the target script (i.e., the local script on 'MyObject'), it's a good idea to also recompile any scripts that reference variables in that script. Reason: if the target 'variable' winds up in a different position in the target script's list of variables, then any scripts trying to set that variable will break if they're not recompiled.

I.e., if 'variable' is the 14th variable declared in the local script on 'MyObject', and you add a new variable ahead of it so that 'variable' is now the 15th variable declared, other scripts will need to be recompiled in order to find 'variable' in its new position, otherwise the script will end up changing the wrong variable, leading to very strange bugs.

One way to reduce the chances of this tripping you up is declare variables referenced by other scripts first, before other variables that aren't referenced externally. Then just be careful to only add or remove variables after the block of externally referenced variables. However (to be safe), it's still a good idea to recompile scripts that reference variables in other scripts any time you've added or removed variables in the other scripts.

Using variables in functions

Unfortunately it is one of the limitations of TES script that only certain functions accept variables as parameters, which poses some definite limits. The type of arguments functions accept is indicated in the list of functions below.

Note: For some functions where both a Get-Function and a Set-Function exist, a workaround can be constructed by using the while function (see below).

Operators / mathematical calculations

You can use the standard operators to do calculations in scripts:

Type Symbol
Addition +
Subtraction -
Multiplication *
Division /

The syntax is as follows:

Set result_var to (var_a + var_b)

Instead of variables, literal values are also allowed. I assume that standard operator precedence applies ( * and / are calculated before +/- ). Since it has not been properly tested I usually always use parentheses to be on the safe side. You can use parentheses according to standard mathematical rules:

set ln to ( ln + ( k10 * math_ln10 ) + ( k2 * math_ln2 ) )

A warning: There are different opinions on the forums on the use of several operators in one line. Some people report a lot of problems with this, I myself have successfully used at least four operators and variables in a single line. There appears to be an issue with very long additions (e.g. adding up more than 20 variables in one line of code) that causes a mod to crash the game upon loading. If this happens, split the calculations to several lines.

Notes by DinkumThinkum:

Eleven variables in a single set statement will cause the following error when loading the game, followed by a crash to desktop:

"Need more room for zero pointers in Script::ReplaceGlobalsInData"

as soon as you click the button to acknowledge the error, CTD. (That script name wasn't any script of mine; it's something internal to the game.)

Six variables in a Set statement work fine. Don't know the maximum number, but it's obviously at least six and definitely less than eleven.

There isn't much in the way of dedicated mathematical functions in TES script. There is the 'Random' function and Tribunal added the 'GetSquareRoot' function (see below). If you need more complex functions, I suggest downloading Soralis' Math Mod (available from here at Morrowind Modding History). It’s a collection of scripts that allow you to do complex calculations. Here is a short excerpt from the readme to give you an idea:

This mod adds the ability to use various math functions within Morrowind's scripts. Specifically, these are the scripts that are added:

Name Check/Done Inputs Outputs Accuracy
MathScripts N/A N/A N/A N/A
MathConstants N/A N/A N/A N/A
SquareRoot 1 math_sqrt math_result, math_imag 7
SineScript 2 math_angle math_sin, math_cos, math_tan 7
ArcsineScript 3 math_arc math_sin, math_cos 6-7
NaturalLog 4 math_log math_result, math_imag 4-5
LogScript 5 math_log, math_base math_result, math_imag 3-4
intPower 6 math_value, math_power math_result 7
intRoot 7 math_value, math_root math_result, math_imag 6-7
Modulus 8 math_value, math_mod math_result 6-7
Antiln 9 math_log math_result 4-5
Antilog 10 math_log, math_base math_result 2-3
AbsoluteValue 11* math_abs math_abs 7
PowerScript 12 math_value, math_power math_result, math_imag 2-3

Unfortunately many of these functions are rather slow, and not suited for real time calculations. For sine and cosine in particular, check out JDGBOLT's script in the tips and tricks section, which uses pre-calculated values for a very rapid calculation. You can also find more information on UESP Wiki.

MWSE (Morrowind Script Extender) also adds trig functions to Morrowind.

Testing Conditions

Use of if… elseif conditions

if (condition) elseif (condition) else endif

Much of TES scripting relies on the use of if… elseif conditions and variations thereof. It is very important to fully understand these, and the conditions that can be used in them to test conditions of the game world to trigger events. In general a condition is "true" when it has the value of 1 (or simply "not 0" (e.g. -1 is "true" in TES Script too) and "false" when the value is 0. There are thus certain functions in the game that return "true" under certain conditions. For example see the GetAIPackageDone function further down. It returns "true" (= 1) for one frame when an AIPackage has finished. All lines of code between the if-statement and endif (which concludes a so-called "if block") will be executed only when the condition is true.

Thus:

If ( GetAIPackageDone )
;[do something here]
endif

will be only executed when the GetAIPackageDone function currently returns the value 1.

In addition to just a value, a condition can also be a more explicit test of conditions. The conditions, such as "A equals B" or "A is greater than B" will be evaluated, and the whole expression will be true (equals 1) when the condition is fulfilled. E.g.:

if ( GetAIPackageDone == 1 )
;[do something here]
endif

Is equivalent to the example above. This second syntax tests a condition and returns "true" if the condition is met. You can check for the following conditions:

Verbose Conditional operator
equal to ==
not equal to  !=
Greater than >
Smaller than <
Greater than or equal to >=
Smaller than or equal to <=

If you have several independent if blocks each will be evaluated separately. If, e.g. the following if blocks are in your script:

if ( timer >= 2 )
    ;[Block A,do something here]
endif
if ( timer <= 3 )
    ;[Block B, do something else here]
endif

Both will be true and the code in the blocks will be executed when timer is between 2 and 3. You can nest if-blocks if you want to make sure that two conditions are fulfilled simultaneously:

if ( timer >= 2 )
    if ( controlvar == 0
        ;[do something here]
    endif
endif

The code in the inner if block will only be executed when both conditions (timer >= 2 and controlvar == 0 ) are fulfilled at the same time.

For more elaborate constructions you can use Else and Elseif. Elseif tests for a separate condition if the preceding condition has failed (but not if the previous condition was fulfilled)

if ( timer >= 2 )
    ; [Block A,do something here]
elseif ( timer <= 3 )
    ; [Block B, do something else here]
endif

unlike the example above, both blocks can not be true at the same time now. Either timer is >= 2, then block A gets executed, or timer is not >= 2 but <= 3, meaning effectively timer is < 2, then block B gets executed. In all other cases, neither block will be executed. You can have several elseifs behind each other:

if ( counter == 1 )
    ; [Block A, do something here]
elseif ( counter == 2 )
    ; [Block B, do something else here]
elseif ( counter == 3 )
    ; [Block C, do something else here]
endif

An else creates a "default condition". The code following else will be executed if all previously tested conditions are false:

if ( foo_var == 1 )
    [Block A: do something]
elseif ( foo_var == 2 )
    [Block B: do something else]
else
    [Block C: do something completely different]
endif

In this example Block C will be executed it foo_var is neither 1 nor 2.

Notes and cautions: In my experience it is safest to use elseif instead of multiple separate if statements if you test different states of one variable.

Be careful using a function in a conditional ('If' statement); simple functions seem to be OK, but ones with numeric parameters don't seem to be reliable. For example:

"If ( NewType != OldType )" worked correctly, but "If ( ( Player->GetArmorType 0 ) != OldType )" always registered as 'Not equal', even if the variable 'OldType' had the same value as the armor type that should have been returned by 'Get ArmorType 0' function. So use "Set NewType to ( Player->GetArmorType 0 )", followed by a separate 'If' statement to compare 'NewType' to 'OldType'. -(DinkumThinkum)

While Conditions

While ( condition )

   ; things to do

EndWhile

The while command differs from the if command in that it is repeated within one frame until the condition is fulfilled. This is best explained with an example:

Short desiredAmnt

SetStrength 0
while( GetStrength < desiredAmnt ) ; non-literal value to match
    modStrength 1
endwhile

This will set strength to the value in variable desiredAmnt after one frame. The following script however would need an undetermined amount of frames to do this, because the if condition is only called once each frame:

if(getStrength < desiredAmnt) ; non-literal value to match
    modStrength 1
endif

On the other hand the first example can potentially cause "freezing" (if the value would be very high) while the second won't.

Note that this is a workaround for some functions to the problem of functions not accepting non-literal values (variables) as arguments.

Constructing Boolean Operations

Unfortunately there are no Boolean operators (AND, OR, NOT, XOR, …) in the scripting language. Thus you need to construct these yourself using if… elseif structures or math.

NOTE: As of 30 June 2015, the expanded MWSE version (v0.9.5 alpha) by Merzasphor includes functions that perform boolean logic (xAnd, xOr, xXor, xNot)!!! :)
These allow doing accurate boolean operations in a cleaner way (similar to in programming languages) - without needing to use the workarounds detailed below.

Instead of AND:

if ( variable1 AND variable2 ); does not exist
    [do something]
endif

you can use:

if ( variable1 )
    if ( variable2 )
        [do something]
    endif
endif

For OR constructs:

if ( variable1 OR variable2 )
    [do this]
endif

you can use elseif constructions:

if ( variable1 )
    [do this]
elseif ( variable2 )
    [do this]
endif

Note that as a general programming/scripting practice, duplicating code is a bad practice that should be avoided whenever possible. Duplication of a section of your code will make your script longer (and less readable) and will force you to also duplicate any changes you would like to make in the future, all in all making the script harder to maintain. You can avoid this by using a temporary flag variable to facilitate OR conditions:

short flag

set flag to 0
if ( condition1 )
    set flag to 1
elseif ( condition2 )
    set flag to 1
elseif (condition3 )
    set flag to 1
endif

if ( flag ) ;one of the conditions was satisfied
    do your thing
endif

You can also implement logical operations by using the math operators, leading to cleaner code (less if statements). If you want an OR operation, store the sum of the conditions and check if it's true (keep in mind this will not be reliable if one of the conditions can have a negative value), if you want an AND operation, multiply all the conditions together and store the result. Unfortunately, unlike in programming languages, it does not seem to be possible to use the comparison operators in set, e.g. set temp to ( a == b ), etc. Examples:

short temp

set temp to ( condition1 + condition2 + condition3 ) ;if at least one of these conditions is true, then temp will be true - barring negative values 
if ( temp )
   do your thing
endif

set temp to ( condition1 * condition2 * condition3 ) ;temp will only be true if none of the conditions are zero
if ( temp )
   do your thing
endif

; for complex expressions you will need multiple set statements. example:
set temp to ( player->GetParalysis )
set temp to temp + ( player->GetSilence ) ;(you can use * here instead of + for an AND effect)
if ( temp ) ; will run only if player is either paralyzed or silenced
   do your thing
endif

External Links

Download at the Nexus

Download at The Assimilation Lab