Scripting with MWSE

From Morrowind Modding Wiki
Jump to: navigation, search

This is a scripting guide for MWSE by Yacoby. Permission was granted by Yacoby to put the guide here on this wiki.

The original PDF version is available for download at these links.

Forward

This guide was written for the purpose of getting people to start scripting with Morrowind Script Extender. It is a wonderful thing, but it can seem hard to make the first leap. I hope people who read this find it easier to make that leap.

This isn’t finished. It is nowhere near finished, so if anyone has anything that they would consider adding, just drop me a note.

I would like to thank the MWSE development team, for writing MWSE, and especially Fliggerty, for the feedback and scripts he provided.

Happy Scripting
~Yacoby

Introduction

What is MWSE?

Morrowind Script Extender (MWSE for short) does what is says on the tin. It extends the number of functions available for use by modders.

For example, using the default scripting language, there is no way to change an objects name. However, if we add MWSE into the mix, all you have to do is use the xSetName function.

It makes things possible that are impossible or very hard with the vanilla scripting engine.

MWSE doesn’t require a fast computer to run, in fact, when running MWSE on my old computer (1.6 GHz CPU, 16Mb Gefore4 MX Graphics card, 256Mb of RAM) I didn’t notice any drop in my frames per second when running MWSE.

How do I run MWSE?

To allow MWSE functions and mods to work, you need to have MWSE running in the background while you run Morrowind.

The most recent version of MWSE can be downloaded from here:
http://sourceforge.net/projects/mwse

With the most current version of MWSE, (0.9.4a) you need to run MWSE Launcher.exe to run MWSE. However, as things change, always check the readme included with your version of MWSE.

How do I start scripting with MWSE?

The first thing you need to know is how to write basic Morrowind scripts. This document assumes that you at least know what if blocks are.

If you don’t know how to script yet, I suggest you look at Morrowind Scripting for Dummies, which can be found at places like
[1]

Make sure you get version 8 or version 9. Unfortunately, you can still find version 5 on a lot of places.

When you make a MWSE mod, you can do it in exactly the same way as you would a normal mod. There is, however, one thing that you cannot do, and that is compile scripts using MWSE functions in the Construction Set (CS).

This means that we need to use another editor called MWEdit to write MWSE scripts. Although MWEdit is far better than the CS in some respects, it cannot edit cells, and I wouldn’t advise writing dialog with it.

This means that in most cases, you will still need to use the CS, as well as MWEdit, to make your mod, so to make sure I get my point across:
You can edit a MWSE mod in the CS, as long as you don’t edit any scripts containing MWSE functions.

MWEdit can be downloaded from here:
http://sourceforge.net/projects/mwedit

Installing MWEdit is an easy matter. All you need to do is extract the files to a location of your choice and run MWEdit.exe

As soon as you start MWEdit, you will notice that the layout is far different from the CS, but once you get used to it, you will wish the CS had MWEdits layout. Trust me on this.

FAQ

(By Fliggerty, edited by Yacoby)

  • Is it stable? - MWSE itself is quite stable. Many people claim that they experience fewer CTDs while using MWSE. However, as with anything involved with Morrowind, it seems that depending on your hardware/software configuration, it may not run very well.
  • Does it eat resources? - While playing the game, MWSE.exe rarely uses up to 4% of the CPU. The most memory I have ever seen it use is 1.6Mb. Upon initial load, before Morrowind starts, it is not uncommon to see it momentarily use 100% of the CPU.
  • Can it work with other external programs? - Yes, in most cases. MGE has a built-in version of MWSE that is equivalent to v0.9.2 or 0.9.3 (Depending on which version of MGE you use). MWSE and MWE are also compatible. However, some people have reported that they will not run together at all. This seems to be system dependent. There are also no known issues using both MWSE and the FPS Optimizer.
  • If so, how to configure?
    • MWE: If you use MWE to autolaunch Morrowind, change the path in "Autolaunch Morrowind" to point to MWSE Launcher.exe rather than Morrowind Launcher.exe.
    • FPS Opt: No configuration required.
    • MGE 3.0.3: MWSE 0.9.4a is incompatible with MGE 3.0.3. If you want to use MGE mods, use MGEs internal version (0.9.2)
    • MGE 3.3.2: MWSE 0.9.4a is incompatible with MGE 3.0.3. If you want to use MGE mods, use MGEs internal version (0.9.3)
  • Are there longer loading times and/or reduced fps? - There are often longer loading times. MWSE scans all scripts when loading the game. This is necessary to determine which scripts contain MWSE functions. Depending on how script-intensive your mods are, this delay may be rather long. As with any mod, if it runs a lot of scripts, an MWSE mod may cause an FPS hit. But any such effect from MWSE itself is not noticeable.
  • Is there spyware? - No. There is no code that will intentionally do anything harmful to your computer. If you don’t trust me, you can download the source and see for yourself.
  • I can't install it! - Extract MWSE anywhere, it doesn’t need to be in the Morrowind directory.
  • Why isn't it working? - Ensure that you are running Bloodmoon v1.6.1820 (If you use GOTY, you don’t need to patch it). You can get the patch here: http://www.elderscrolls.com/downloads/updates_patches.htm. Make sure you have the latest version of MWSE.
  • Will it work with a no-cd patch? - Yes. It seems you have to have the proper one though. I use this one: http://www.gameburnworld.com/dl/dl.php?file=MorrowindBloodmoonv1.6.1820NoCDFixedexeEng.rar

Quickstart Guide

Happy to jump in, ignoring the tutorials? This is the section for you. As much information packed into as little text as possible. When you have read this, you will need to look over the function list to get an idea of what functions are possible.

Variables

Types of MWSE Variables

The MWSE functions make use of two new variable types, string and reference. Both are actually declared in the scripts using the "long" type.

The ref, reference, variables provide a way to keep track of specific objects in the game world. They are most commonly used on the left side of the -> operator with other functions (new or old) to affect that object.

Long temp
Long var
Setx var to xGetRef, “Player”
;the variable var is now a ref, pointing to the player
Var->xItem, “Gold_001”, 2
Setx var to xStringBuild, “Happy”
;the variable var is now a string
Setx temp to xStringComapre, var, “Sad”
If ( temp == 0 )
    Messagebox, “Someone is sad”
endif 

All string and ref information is lost when Morrowind is closed.

Using setx

Unfortunately, the new functions can't be easily integrated with the standard script functions and commands. A 'setx' command is provided as a substitute for 'set' when you are using a new function. You can only put one function or one string value on the right side of 'setx' but it does allow more than one variable on the left for functions like 'xInventory' that return multiple values.

The MWEdit compiler doesn’t do any error checking on the number of variables on the left hand side, so don’t get it wrong.

A normal Morrowind function using set
Set health to GetHealth

A MWSE function using setx
Setx pcCell to xPCCellID

Multiple returns using setx
setx invitem,invcount,invref to pcref->xInventory

Testing Conditions

Ifx, Whilex conditions

The standard Morrowind script if and while commands do not work when the body of the block contains extended commands, instead use ifx and whilex.

The ifx and whilex do not currently support a full syntax, it doesn’t compile if a function is used in the ifx statement itself

Ifx ( getHealth ) ;bad

Float tmp
Set tmp to getHealth
Ifx ( tmp ) ;good

The ifx/whilex block will be performed if the value is not zero. You can use 'else' with 'ifx' but not 'elseif' and there isn't an 'elseifx'.

If you have a multiple stage if/elseif section it's frequently possible to isolate the new functions to only one or two sections, so check all of the others first then use statements like the following.

Old Version New Version
if ( state == 5 )
 ; section for 5

else

 ; section for not 5

endif

set temp to ( state - 5 )

ifx ( temp )

 ; section for not 5

else

 ; section for 5

endif

It's OK to leave the first section blank, so this also works:

Old Version New Version
if ( state == 5 )
 ; section for 5

endif

set temp to ( state - 5 )

ifx ( temp )
else

 ; section for 5

endif

This makes some scripts very messy. If you have a lot of if statements that results in a single block of code either being executed or skipped, sometimes it is easier to do something like this, rather than a load of complex ifx blocks.

Set temp to 0

if ( player->getHealth > 0 )
    if ( state == 5 )
        if ( player->getLevel == 25 )
            Set temp to 1
        endif
    endif
endif

ifx ( temp )
    ;do extended functions here
endif

Also be careful with reference variables.

long NPCref
short state

Setx NPCref to xFirstNPC

if ( state != 0 )
    NPCref->startscript myscript ; this will not work
endif

ifx ( state )
    NPCref->startscript myscript ; this will work
endif

Data Loss

When you exit and reload Morrowind, all string and ref variables become invalid.

Tutorial One

Open up MWEdit, and click OK to the warning screen that comes up. Don’t disregard what it says though; it is always worth backing up a plugin once every few days.

Press the “New” button (The one that looks like a white piece of paper).

Scroll down the left hand list box till you find an item that says “Script”. Click on it. Now in the larger right hand box, right click and press “Add New” and a new script dialog should come up.

We are going to write a small script that will tell the player what cell he or she is in.

Start the script

Begin My_First_MWSE_Script
End My_First_MWSE_Script

The first thing we need to do is find and store the cell that the player is in. So we look at the MWSE function reference (at the end of this document) and find:
cellid (string): xPCCellID

Returns a string containing the name of the current cell.

Which all looks rather confusing.

  • cellid is what is returned, basically the name of the cell the player is in
  • (string) is the type of the object that is retuned.
  • xPCCellID is the function name

So what is a string I hear you say. A string is a collection of characters; “Hello World!”, “I am 354 years old” and “a” are all examples of strings. A string must always be surrounded by quotes.

MWSE cheats a bit with strings, rather than have a new String variable type, it uses the Long variable type.

So next thing we need to do is add the variable to hold the players cell.

Begin My_First_MWSE_Script

; stores the cell the player is in
Long playersCell

End My_First_MWSE_Script

Then, we need to set the variable to the players cell.

One important thing about MWSE functions is that rather than use the set function, you need to use the setx function.

So with a normal function, you would use set:
Set myHealth to getHealth

With a MWSE function, you would use setx:
Setx myCell to xPCCellID

Bearing this in mind, we will store the player’s cell in the “playersCell” variable:

Begin My_First_MWSE_Script

; stores the cell the player is in
Long playersCell

; make the variable hold the players cell
Setx playersCell to xPCCellID

End My_First_MWSE_Script

Now, you may have noticed that this doesn’t actual do anything, so we need to output the players cell using the messageBox function.

Unfortunately, you cannot output MWSE strings in normal messageboxs, but luckily there is a fix, xMessageFix to be precise. If we look at xMessageFix in the MWSE function reference, we find quite a lot of information. However, not that much of it is useful here, as all we want to do is display a string, not use choices.

Take a look at the xStringBuild function, you will see that the arguments are exactly the same as xMessageFix, for the purpose of formatting messages; xMessageFix and xStringBuild are exactly the same.

The most important thing to remember about xMessageFix is that it must be followed by a messagebox, and the length of the message in the messagebox must be longer than the length of the message in xMessageFix

xMessageFix message_format (string), ...

The first argument for the xMessageFix function is the message format, the second, third, forth are the variables defined in the message_format argument. (They can also be choices.)

What we want to do is display “You are in CellName” in a messagebox.

%s is used by the xMessageFix function to insert a string into a message. As we have two strings, we would use two %s in the format argument:

xMessageFix, “%s%s”, “You are in ”, playersCell
Messagebox, “ThisJustFillsTheMessageboxUpWithRandomJunk.ThisWillBeReplaced”

However, there is a slightly tidier way of doing things, as we don’t just have to put references to strings in the format box:

xMessageFix, “You are in %s”, playersCell
Messagebox, “ThisJustFillsTheMessageboxUpWithRandomJunk.ThisWillBeReplaced”

So integrating this into the main script, and we get:

Begin My_First_MWSE_Script

; stores the cell the player is in
Long playersCell

; make the variable hold the players cell
Setx playersCell to xPCCellID

; tell the player what cell he or she is in
xMessageFix, “You are in %s”, playersCell
Messagebox, “abcdefghijklmnopqrstuvqxyz0123456789abcdefghijk”

End My_First_MWSE_Script

You may have noticed that the script will just contiusly output the message, so after the messagebox, we need to stop the script:

Begin My_First_MWSE_Script

; stores the cell the player is in
Long playersCell

; make the variable hold the players cell
Setx playersCell to xPCCellID

; tell the player what cell he or she is in
xMessageFix, “You are in %s”, playersCell
Messagebox, “abcdefghijklmnopqrstuvqxyz0123456789abcdefghijk”

stopScript, My_First_MWSE_Script

End My_First_MWSE_Script

Now press the compile button in the script editor. It should compile without any errors. If you get an error about “My_First_MWSE_Script” being an invalid object, you need to save the script so that it exists in the esp, open it again and then try and compile it.

Now give the script a test in game (don’t forget to start MWSE as well).

Tutorial Two

In this next script, we are going to make a script that when you look at an NPC, it will shrink it, and when you look at a creature it will make it grow in size.

The first thing we need to do is get the target of what the player is looking at:

Begin rescale_script

Long pcTarget

End rescale_script

The function xGetPCTarget returns the id of the object that the player is looking at. It will only get the target of objects where the name box shows up, so it doesn’t work getting the target of things that are in combat with you.

If there is no target, it will return 0:

Begin rescale_script

Long pcTarget

Setx pcTarget to xGetPCTarget

if ( pcTarget == 0 ) ; nothing targeted
    Return
Endif

End rescale_script

The variable pcTarget now holds a reference to the players target. So with extended functions, we can use the -> operator and the variable.

Next we need to work out what type of object the player is looking at. For this we need to use the xRefType function. xRefType returns a different set of numbers depending on the object type. If the object is a creature, it returns 1095062083, and if it is an NPC, it returns 1598246990.

To hold the number we are going to create a new variable called temp, which we will use to store the number. We will also create a variable to say if it is a creature or not, because this variable will be 0 or 1, we can use it very easily with the ifx command:

Begin rescale_script

Long pcTarget

Setx pcTarget to xGetPCTarget

if ( pcTarget == 0 ) ; nothing targeted
    Return
Endif

Long temp

Setx temp to pcTarget->xRefType

Short isNPC

if ( temp == 1598246990 ) ; it is an NPC
    Set isNPC to 1
Elseif ( temp == 1598246990 ) ; it is a creature
    Set isNPC to 0
Else ; we don’t want to rescale other objects, so return
    Return
Endif

End rescale_script

So, to recap, we now have a variable that tells us if it is a creature or a NPC. If it is anything else, we have used the return function so it won’t be executing this part of the script. We also have a variable holding a reference to the object the player is looking at.

What we next need to check is the object hasn’t already been scaled. (Changing the scale of an object is a very expensive operation for the game engine to do).

Unfortunately, we cannot use the -> operator with a ref variable and a standard function (any function that doesn’t use setx):
Set temp to pcTarget->getScale ; Doesn’t Work

Fortunately there is a workaround; xSetRef. xSetRef makes the next function called apply to the ref argument given to xSetRef.

This code:
xSetRef, pcTarget
set temp to getScale

Does exactly the same thing as this code does:
Set temp to pcTarget->getScale

Except it works.

So we need to add what we have learnt into the script:

Begin rescale_script

Long pcTarget

Setx pcTarget to xGetPCTarget

If ( pcTarget == 0 ) ; nothing targeted
    Return
Endif

Long temp

Setx temp to pcTarget->xRefType

Short isNPC

If ( temp == 1598246990 ) ; it is an NPC
    Set isNPC to 1
Elseif ( temp == 1598246990 ) ; it is a creature
    Set isNPC to 0
Else ; we don’t want to rescale other objects, so return
    Return
Endif

; get the scale
xSetRef, pcTarget
set temp to getScale

; if the scale is not 1, it has already been changed, so we don’t need to change it again
if ( temp != 1 )
    return
endif

End rescale_script

At this point in the script, we know we need to rescale the object, but we need to decide if we need to increase the size (for creatures) or decrease the size (NPCs).

Unfortunately, you cannot use extended functions within normal if blocks, you have to use the new ifx blocks. The ifx blocks have several disadvantages, the main one being that they don’t do any of the more complex checking that the normal if blocks have. It will execute the block if the value in the ifx is true (anything other than 0), other wise it skips it.

There is more about ifx and whilex MWSE Quickstart guide.

As we have a variable that holds whether it is an NPC or not in a single 1 or 0 variable, it will be very easy to implement:

Begin rescale_script

Long pcTarget

Setx pcTarget to xGetPCTarget

If ( pcTarget == 0 ) ; nothing targeted
    Return
Endif

Long temp

Setx temp to pcTarget->xRefType

Short isNPC

If ( temp == 1598246990 ) ; it is an NPC
    Set isNPC to 1
Elseif ( temp == 1598246990 ) ; it is a creature
    Set isNPC to 0
Else ; we don’t want to rescale other objects, so return
    Return
Endif

; get the scale
xSetRef, pcTarget
set temp to getScale

; if the scale is not 1, it has already been changed, so we don’t need to change it again
if ( temp != 1 )
    return
endif

ifx ( isNPC )
    xSetRef, pcTarget
    setScale, 0.5
else ; it is not an NPC, so it must be a creature
    xSetRef, pcTarget
    setScale, 1.5
endif

End rescale_script

The last thing we need to do is add some simple optimization. As this script will not need to be run when there are is a menu open, we can make it skip executing the script when there is a menu open by adding this to the top of the script:

If ( MenuMode )
    Return
Endif

Tips and Tricks

Storing Cell/Position Data

As you know, all string data is lost when Morrowind stops running. If you want to store a cell ID, how do you do it?

If you know how many positions need to be stored, one solution is to use disabled NPCs or Creatures. Place them in the position that you need to be stored. You can later retrieve the information via xGetRef, then using getPos, and xMyCellID.

Pros:
The data is stored in the save, so it is unique to that save.

Cons:
You have to know how many positions need to be stored, and create an NPC for every position.

Another method is to write the data to another file. You could, in theory, store an infinite number of locations. However, the problem comes making sure the data is unique to every save. You could just include the PC name in the file name, but then what would happen if you had to players with the same name? When you went back to a previous save? You would have to include enough unique data in the file name that the file name was unique. You could, for example, use the cell name and the players name, and rewrite the data every time the player changed cell.

Pros:
Can store an unlimited amount of data.

Cons:
You need to use a lot of information from the player to make sure the file is unique. As there is no delete function the MWSE directory may end up getting full of unneeded files.

Sending Data to other applications (Programmers only)

(By CDCooley)

// 2005-07-13 CDC This is a very simplistic example for creating a named pipe.
// The MSDN documention for CreateNamedPipe gives a multi-threaded demonstration.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#define BUFSIZE 4096
int main(){
    HANDLE pipe;
    DWORD size, i;
    CHAR buffer[BUFSIZE+1];

    //From Morrowind, this pipe is called demo1
        pipe = CreateNamedPipe("\\\\.\\pipe\\MWSEdemo1",
            PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE |
            PIPE_READMODE_MESSAGE | PIPE_WAIT,
            PIPE_UNLIMITED_INSTANCES, BUFSIZE, BUFSIZE, 20000, NULL);

    while ( (pipe != INVALID_HANDLE_VALUE) &&
          ( ConnectNamedPipe(pipe, NULL) ||
          (GetLastError() == ERROR_PIPE_CONNECTED) ) ){
          // A simple echo server
          while (true){
              if (!ReadFile( pipe, buffer, BUFSIZE, &size, NULL) || (size == 0) ) break;
                  buffer[size] = 0; // ensure it's null terminated in case it's a string
                  printf("Request as float=%f as long=%d as string=%s\n",*((float*)buffer),*((long*)buffer), buffer) && fflush(stdout);

                  if ( !WriteFile(pipe, buffer, size, &i, NULL)
                                            || (i != size) ) break;
          }
          FlushFileBuffers(pipe);
          DisconnectNamedPipe(pipe);
    }
    CloseHandle(pipe);
    return 0;
}
begin mwse_pipeexample

long count
long numread
float sent
float received

if ( MenuMode == 1 )
    return
endif

ifx ( count )
    xFileWriteFloat "|demo1" sent
    set received to 0.0
    setx numread received to xFileReadFloat "|demo1" 1
    if ( numread < 1 )
        MessageBox "I sent %f, but the server didn't respond! Maybe it's just slow. Or maybe it's not running." sent "OK"
    else
        MessageBox "I sent %f and received %f." sent received "OK"
    endif
    set sent to ( sent * sent )
    set count to ( count - 1 )
    if ( count == 0 )
        StopScript mwse_pipeexample
    endif
else
    set sent to 3.14159
    set count to 5
endif

end

MWSE Function Reference

Main article: Functions

MWSE adds a total of 98 new functions to Morrowind's scripting language. These range from wrappers, to brand new functions, from adding new mathematical functions, to adding file I/O.

External Links

Download at the Nexus