I'm not really a Morrowind modder. Morrowind modders are like hackers, only for Morrowind and its Construction Set. I'm intimidated by their highly technical discussions on how to produce a certain effect that should be easy to produce, only the gamemakers, having made an RPG, thought that fans would want to create RPG-type mods with a few monsters to kill, some treasure to snatch, perhaps some dialogue and text for the diary. What fans in fact want to create is a complete personalized virtual playground using Morrowind's game engine. It's a bit like how Maxis underestimated the kind of customization that Sims fans wanted to make. Morrowind is slightly worse: firstly, not all syntactical features are covered in the helpfile, and some are broken. Secondly, conditions that should be easy to test for, are not. Take swimming. When a character is deep enough in water, the wading animation changes to a swimming animation. So the game "knows" the character is in water. Now I need a script that asks "is the character swimming or not" because I've added a bottle that can be filled with water, but only when there is water near, and water is certainly very near when you're up to your neck in it. Well, even though the game "knows", there is no way of asking it. The only, very roundabout, way of knowing if your game character is swimming, as I learned on a forum discussion concerning a bathing mod, is to ask the game whether any of the splish-splash swimming sounds are being played. For your own character, that is. I don't know how I would go about testing this for a companion character. (I do now.)
What I am is, in all senses of the word, an amateur. The game booklet said I could add and change things, so I did. Or rather, I tried to. I made a relatively easy mod to set things right concerning a certain Fargoth, and embarked on a much more difficult mod to teach the arrogant Dunmer (or rather, one arrogant Dunmer) a lesson in humility, which I soon became stuck on. I don't even know if I'll ever finish it. But in the course of doing so, I educated myself on scripting, mostly through version 9 of the fan-written Morrowind Scripting for Dummies (MSFD9) and discovered a few things, not all of them mentioned in this document. (Update 2021: while this is the final version of the guide, Tamriel Rebuilt has a page of errata and addenda.) So, for anyone surfing on the quirks of Morrowind modding, I thought I'd share.
After using the latest patch and finding out it wasn't really the latest patch (which means some quirks may have been due to not having used the most up-to-date executable) I've split the list up into real quirks and technical issues. Update 2021: the list of quirks got longer and longer, so I split it into mistakes to learn from, just quirks, and how to (including how to deal with any quirks involved).
Not to do with modding itself, but handy when testing mods. Two keyboard shortcuts I found on Morrowind sites while surfing: if you want to take something out of your inventory of which you have more than one, for instance: 9 Cure Disease potions, holding down the Ctrl key while clicking on the item will pick up only one item and skip the usual question of how much to take, while holding down the Shift key picks up the whole lot without question. I also read that as long as you hold down the Ctrl key, the number of times you click on the item will be the number of items picked up, but that didn't quite work for me; Ctrl-click did pick up only one of a bunch of items, but Ctrl-click-click picked it up and put it back again. Ctrl-click-click-click picked it up, put it back and then picked it up again. I was never able to pick up more than one item this way.
Not undocumented, but simply a default keybinding that I discovered by accident before I saw that it can be changed in the game options: pressing Q once is the same as keeping W pressed down: the character keeps walking. Press W, or Q again, to stop the walking. Opening a menu temporarily stops the walking. Q also works with Shift (run) and Ctrl (sneak). Using Q and Capslock saves wear on the keyboard. (The Q key functionality was added in a game update, and is mentioned in the game's "readme.txt".)
| Back to technical issues |
The following is explained in Bethesda's own "readme.txt", which I didn't bother to re-read after installing the expansions, so I found it through websurfing:
When opening any plugin in the GOTY TESCS, be ready for many error messages
about preceding and following strings being different. And when error-checking
dialogue, expect a lot of errors too. For the first, a setting is needed which
is not in the Morrowind.ini, so this whole line has to be added:
AllowYesToAll=1
If you get text string errors on loading in the TESCS, a third "cancel" button is added to the "yes/no" buttons, and clicking on this button skips the other error messages.
For the second, the only real errors are the name of Fons Beren's journal and
the nonexistent stock certificate journal, which ought to be a global variable.
The other "errors" disappeared after I'd added a lot of code to result fields of
my own.
| Back to technical issues |
The TESCS that comes with the original Morrowind does not ask to save a changed plugin before reloading. The TESCS with Morrowind GOTY fortunately does.
The GOTY TESCS also lets me indicate which journal line is the quest title, and which line is the quest ending. This is for the ordered display of quests which was so sorely lacking in the original, and was introduced in its expansions. The space used for these indications is space left blank in a Morrowind-only journal, so a plugin with journals created in the GOTY TESCS can probably be safely used in plain Morrowind, providing that the plugin does not contain any Evil GMSTs (expansion game settings) or startscripts.
Here's something that both TESCS's do: I add a lot of dialogue for Fargoth, about half of it also for the cell "Seyda Neen, Fargoth's house" since it has to be said in the privacy of his own house. I filter all dialogue for "fargoth" to check it. The dialogue that is also connected to a place, like Fargoth's house, does not show up. That's strange, because otherwise I get all Fargoth-related dialogue, not only what Fargoth personally says, but also lines for anyone of his race and/or faction.
I've read that the different TES versions compile animations differently because the animations are coded in a different way internally not only between Morrowind and Tribunal, but also between patched versions of the same game.
One funny thing that happened in the GOTY TESCS: I load a Morrowind-only plugin, so of course the TESCS automatically adds all Tribunal/Bloodmoon GMSTs (the so-called Evil GMSTs) to the file. Discovering this, I load it again, this time with Wrye's "GMST vaccine" plugin which ensures that these GMSTs are not added to the active plugin. Clicking on Data Files in the menu and then on Details in the data files box, I select, and mark for deletion, all GMSTs in what I think is my plugin, only there is a suspiciously large number of them, and they cover many basic settings; yes, I didn't select my plugin properly, and am now marking GMSTs in Morrowind.esm. The TESCS will not save changes made to an ESM, but to be sure, I unmarked the deletions again before loading my own plugin, marking the Tribunal/Bloodmoon GMSTs, reloading it and saving it to be rid of those GMSTs forever.
Or so I thought. Because the next time I started a game, menu buttons suddenly had "s" prefixed to them, for instance "sWait" in the sleeping menu and "sGold" in the bartering menu. Extra "s" characters turned up in other places as well, like the dates in the journal. I saved the game, quit, looked at Morrowind.esm which did suddenly have today's date even if its size hadn't changed, copied the original from a backup installation over it to be sure, opened the TESCS again, chose my plugin and looked at the details. There they were: all the GMSTs that I'd "deleted" from the Morrowind ESM, even if I'd cancelled that action, even if they could never be deleted in the first place, had been instantly added to my plugin, and it didn't even show until I'd closed the TESCS and opened it again. Of course, deleting them solved the problem.
Again in the GOTY TESCS, although this probably applies to the Morrowind-only
version too: it's said that the script compiler is too lenient, which I'd
already discovered regarding GetDeadCount (see GetDeadCount and
the prefix) and not complaining about the use of local variables which do
exist, but were not declared in that particular script (see Local, global, targeted, variables, somewhere in the middle of
that big block of text). But when I checked all the scripts I'd made in
MWEdit, a fan-made editor which emphatically informs the user on startup
that it's a BETA, I saw the following three errors that the TESCS ignores:
if ( Condition = true ) ; AAGH! single "=" instead of "=="
set localvar to localvar + 1 ; should be "( localvar + 1 )",
game doesn't seem to mind
if ( Condition = true )
ModSomething 50
endif
; GAH! Extra endif not detected because there's no check whether if-
blocks start with "elseif"!
elseif ( OtherCondition == true)
ModSomething -50
endif
endif
Especially the third mistake can really break a script. MWEdit, given its
beta status, isn't perfect either; in targeted scripts it fails to see that
"mycompanion.RemoteLocalVar" might actually exist, it calls big outdoor areas
like "Ascadian Isles Region" not a cell and won't accept such a string with
PositionCell, and in the result field, it refuses to process any lines under a
"Choice" line, even if they have nothing to do with it, saying that you can't
process anything within a Choice function. Well, that is true: I did once try to
set a global variable to 1 in a result field line starting with "Choice", and
Check Errors reported that this global didn't exist, even though it clearly did.
I've since encountered two other really stupid mistakes that the TESCS script
compiler (both versions) let pass without comment, although the game rightly
balked at them:
if ( Variable <> 0 ) ; should be "!="
set Var1 to = ( Var2 + Var 3 ) ; unwanted "=" sign
The second was a stupid copy/paste mistake causing a "Set" error message,
the first was a matter of using an operator that the game engine does not
recognize, and alternately interprets as "greater than" or "smaller than",
leading to inconsistent behaviour.
bookmark 2021 mwedit link dead? openmw editor is perfect for scripts
| Back to technical issues |
When the game has a CTD (Crash To Desktop) this can have a number of causes, like script errors and trying to delete an object that is inside someone's inventory. Or the laptop battery may be almost empty. I work on a laptop and, to conserve the battery, let it run empty before recharging it, instead of having the laptop plugged in all the time. While playing, I can't see the state of the battery, and I tend to forget the time, so often either the computer announces that it will go into hibernation - and when waking it up, the game will crash after a while - or the screen just goes black and when I have my desktop back, I see the battery icon with a red cross through it. Sudden unexplained script errors can also be a matter of the battery running empty. A CTD can also occur just before an extra bit of animation is about to occur, notably the player dying in a fight with many creatures and/or NPCs, even when the battery is not almost empty. It seems that whenever the graphics card doesn't get the power it needs, the game shuts down.
When a dialogue screen (especially the character creation dialogue) is opened
while the computer goes into hibernation, the game may, after a restart,
continue without problems. When a book or journal screen is open, it will crash.
I've replaced the "low battery" popup with a jarring system sound (can be heard
even through the background music) to reconnect the laptop to a power source on
time and not lose all unsaved changes due to the computer going into
hibernation.
| Back to technical issues |
The official plugin "Siege at Firemoth" causes problems with a lot of
fan-made plugins. This doesn't mean that there is anything wrong with the
fan-made plugins. I'm still thinking what to do about that. Changing the file
date to make that plugin load last hasn't helped. (The problem mysteriously
resolved itself, see DaysPassed turns up in Morrowind-only
installation.)
| Back to technical issues |
I thought this was a problem caused by playing an old 32-bit era game on a computer with a 64-bit processor and a 64-bit version of Windows 7, until I found I had a 64-bit processor and a 32-bit version of Windows 7. I'm still not sure what the exact reason is, but it has to do with "rundll32.exe". Usually, the game starts without problems. Sometimes, I have to click the icon twice before the game runs, and it will be slow and choppy, a Desktop gadget showing the CPU at 100%. Using Ctrl-Alt-Del to find what was running, I saw that the process hogging the CPU was "rundll32.exe". Deleting this process did not stop the game, but sped it up to its normal framerate. So I found a workaround: start Morrowind (click icon twice if necessary), this opens the Morrowind Launcher. Open the Task Manager and delete the "rundll32.exe" process. Then start the game.
I revisited this problem when I wanted to install the Morrowind Script Extender. This is as simple as putting the MSE executable in the same directory as the Morrowind executable and starting Morrowind by double-clicking the MSE executable, instead of Morrowind.exe. If I double-clicked the MSE executable, nothing happened; if I then double-clicked the Morrowind executable, it would start, but crash if I wanted to start a new game (as there were no save games to load). So I looked again at what happened if I started Morrowind in the standard way: first "rundll32.exe" is run but exits, it runs a second time and stays in memory this time, then the Morrowind Launcher opens and the game can be started from there. If I don't use the Morrowind shortcut but double-click on Morrowind.exe, the game starts directly without loading "rundll32.exe", and without lagging. So the problem is the Morrowind Launcher. Fortunately, the textfile included with the Morrowind Script Extender explains how to make a shortcut that will bypass the Launcher and start Morrowind directly.
Update: I've found that Oberon Media games ran slowly under Windows 7 because Windows was constantly checking for a network connection; disabling all network devices in the system settings solved the problem. It also seemed to solve the "rundll32.exe" problem with the Morrowind Launcher, although I haven't tested that further because nowadays, I only run the game directly from "Morrowind.exe", and only start the Morrowind Launcher to change what mods are loaded, then shut it down again; neither causes lag, and I get to keep my internet connection.
Another update: having bought a new laptop with a 64-bit processor and installed a 64-bit version of Windows 7 on it, I get the "rundll32.exe" problem rarely, if at all; the game may take a bit of time to start, but even if I click its icon twice, the first instance doesn't hang in memory but simply quits with a "game already running" message.
Hopefully final update: "rundll32.exe" is needed for the Morrowind Launcher,
but, under 64-bit Windows 7, does not always nicely quit after doing its job.
This is annoying as it is loaded twice - once by the Morrowind Launcher which
needs it already in memory to successfully run, and again when the Morrowind
Launcher is clicked a second time and runs. This DLL is a resource hog, and
manually ending it via Ctrl-Alt-Delete may contribute to avoiding game crashes.
What certainly doesn't quit nicely is Morrowind itself. Unless the player
character has been left to idle a bit and any saves have been made a minute
before - and sometimes even then - exiting the game leads to crash messages and
a lifeless game window that, again, may need Ctrl-Alt-Delete to close it down
and return to the Desktop. This game-exit crash could corrupt savefiles, but
seems harmless otherwise. (I've read that Morrowind and Alt-Tab, which is needed to kill the process, don't get along.)
| Back to technical issues |
What it says on the tin: I'd downloaded a no-CD crack for the complete game
including expansions by GimpsRUs, and MSE would not run with it. After many
tries and much frustration, I spotted the cracked executable's version number,
which started with "15"; although it claimed to be the latest patched version,
clearly the final patch (number 1820) had come out after it was made. So I found
another crack by DRUNK which did use the right version.
| Back to technical issues |
Collision boxes are the invisible boundaries of a 3D model's "personal space". If a 3D model had the shape of a cube, its collision box could be exactly as big as its outline. But 3D models often have very irregular shapes. Take a bush, for instance. It will be widest in the middle. But if one were to draw a box around the bush that would totally contain it, there would be plenty of free space at the top and bottom that is not occupied by the bush, yet falls within this box. Collision boxes serve, as the name suggests, to detect collision: to see when shape A touches shape B. If a real person walks into a wall, the solidity of that wall is enough to stop that person moving, and the person will be sensitive enough to be (often painfully) aware of having just collided with a wall. But 3D objects rendered in a computer game are neither solid nor sensitive, and their collision boxes are used to fake both.
I found this out when constructing a cosy little dungeon that starts with a slope leading into a main room which then sends out several corridors into other, lower rooms. One of the corridors ran exactly under the slope, but with plenty of room between them. Nevertheless, when a character walked down the slope, I would see that character sink into the floor up to the armpits, to finally drop into the corridor below. How could this happen?
Looking at the collision boxes in the editor made everything clear. The collision boxes of the slope and underlying stairway touch each other. This is because both collision boxes are very high to stretch from the highest to the lowest point of these diagonal shapes. Since they touch, the game is confused about where one ends and the other begins, and the character drops from one into another.
I replaced the slope with sections of straight corridor, which, being almost cube-shaped, had collision boxes barely bigger than themselves. No more touching. Problem solved.
Needless to say, this also happens in the unmodded vanilla game, when bounding boxes overlap. That's why, after some jumping around, I found myself buried up to the waist in the walkway leading to Seyda Neen's lighthouse; I was standing on a rock just underneath it. Continued jumping got me out and on the planks again.
As an amusing aside, collision detection seems to be turned off between NPCs who are following the PC; they tend to all wind up in the same position, especially after passing through loaddoors (doors that teleport the PC to another cell). Their overlapping meshes lead to funny visuals like a Khajiit with Argonian horns.
| Back to technical issues |
This is really silly, but something that took me years to discover, and all
this time I've been typing my fingers off. The console command line, being a
command line, doesn't accept Windows-style copy and paste with Ctrl-C and
Ctrl-V. What it does accept, like a bash or in some cases MS-DOS command line,
is the cursor-up and cursor-down keys to scroll through the history of commands.
So, if I need to issue a series of similar commands, I just go to the previous
command, alter it and press Enter.
| Back to technical issues |
It can happen to any companion, ie. NPC in AIFollow mode. I'm briefly out of
reach and the companion runs up to me and then keeps running around as if
chasing an invisible rabbit. Or I use Almsivi Intervention, remember I've left a
companion behind, go back using PositionCell in the console and the companion is
dashing around, madly trying to get to some unreachable point and ignoring me.
No amount of approaching them and starting dialogue to make them follow will
cure them of this behaviour, so I've always reloaded the latest save.
Apparently, judging from other modders' companion scripts, StartCombat will
break them out of this rut. If so, typing "NPCname->StartCombat NPCname" in the
console, followed by a StopCombat command, should safely calm them down.
| Back to technical issues |
Vanity mode and 3rd person view seem to be the same thing, and they do overlap, so I'll explain what either does. Toggling between 3rd person view (looking down on player character's whole mesh) and 1st person view (looking straight in front of the player character and seeing just its wrists, hands and any held shields and weapons) is done with the Tab key. In either mode, moving the mouse makes the player character look around; holding down Tab in 3rd person view while moving the mouse changes the camera, allowing the player to be looked at from different angles. The related script/console commands are PCGet3rdPerson (which returns 1 if the view mode is 3rd person), and the view mode changers PCForce3rdPerson and PCForce1stPerson.
Vanity mode is a special type of 3rd person view that happens when the user
hasn't interacted with the game for a while, in the same way as a screensaver,
and only when it has been enabled. It shows the player character in 3rd person
view with a slowly rotating camera. Pressing Tab, or any key at all, will end
vanity mode view just as it would end a screensaver, and return to the previous
view mode, although while in vanity mode, PCGet3rdPerson will return 1. This
mode's commands are EnableVanityMode, DisableVanityMode and
GetVanityModeDisabled. The enabling and disabling commands don't instantly start
or stop the vanity view mode, they just tell the game whether to have this
screensaver feature. Switching to vanity view is done with the strictly console
command ToggleVanityMode, or its shortened version TVM, as using any key inside
the game itself will instantly end this mode.
| Back to technical issues |
The snowflake glitch is the error message that a snowflake texture can't be found, when starting Morrowind with Bloodmoon installed. It can mean that the Bloodmoon.bsa file is damaged, or (especially if installed to C: under Windows Vista or later) the user doesn't have the rights to view that file (which is why the game should be installed as administrator). Or it can mean that there are two parallel installations of Morrowind, and Windows is looking in the wrong directory.
I have vanilla Morrowind and Morrowind GOTY Edition (Game of The Year, contains both Tribunal and Bloodmoon) installed side by side. First, Morrowind was installed, and all changed/added keys were saved in a .reg file. There are various utilities to track registry changes, or the whole registry can be saved to file before and after installation, and the two files compared. Then, GOTY was installed to a different directory, the relevant registry keys were saved to a .reg file again, the two files were compared, and the important differences saved as "MW.REG" and "MWGOTY.REG". This had to be repeated any time I reinstalled the game on a newer computer, as I found the registry key formats differed subtly between different Windows versions.
There are a few registry keys essential to the game, namely those that tell it where to look for its game files. Starting "MWGOTY" while the path in the registry points to "MW" is one way to produce this error. So, before I start either game version, I first have to click on the right regfile to adjust the game path.
A double install may also do strange things to the Morrowind entry in the Game Explorer. I searched for "GameUX" in the Windows utility RegEdit (beware, Game Explorer entries are stored under both general and user-specific headings) and fiddled around in the registry until I had two separate, functioning shortcuts, which took at least one reboot.
NB. the "MW.REG" and "MWGOTY.REG" from a 32-bit Windows can't simply be
transplanted to a 64-bit Windows (or from one Windows version to another,
probably) because installing the game changes the registry in a different way
(in the case of 64-bit Windows, the game goes under a special "32-bit programs"
heading). I had to go through the whole installation and registry key saving
again to make a new set.
| Back to technical issues |
Morrowind mesh files contain the names of their textures. In the case of textures that are directly in the "Data Files\Textures" directory (or the equivalent place inside the game BSA files), only the texture filename is needed, without a path. However, if the textures are in a subdirectory, say, "Data Files\Textures\Mystuff", the texture name in the mesh file should include "Textures\", as in "Textures\Mystuff\MyTextureFile.dds". If mods include meshes that look white in the game, as in Horny Buddha's pack zebra mod, this is probably because the textures are in a subdirectory, and the mesh's texture file path doesn't include "Textures\". Fortunately, this can be easily changed in the fan-made NIF tool NifSkope.
The Better Heads textures that I use as editing bases are in the low-quality DDS format DXT1, and so a bit blocky. Apparently, to get the highest quality, I should save in "888 DDS" format without mipmaps. This does give best quality at the cost of a huge increase in filesize, but here I discovered something strange: the texture must have an alpha map. I tried saving as DDS 888, (24-bit colour) but it had to be 888.8 (32-bit colour of which 8 bits alpha) or the texture wouldn't show up in the game. A good compromise is to save face textures in DXT3 format, which includes an alpha buffer, and is the format used for book illustrations.
Books, whose content is written in simple HTML, need their last sentence finished with a line break (<BR>), or the text won't appear. They don't display tables. And they are very fussy about illustrations. Simply using "<IMG SRC="Mystuff/pic.tga">" with a Targa file "pic.tga" in subdirectory "Data Files\Book Art\Mystuff" will not work. Although the game uses Targa files for their alpha buffers, which make the image's background transparent so it can overlay the page, the only format accepted from modders seems to be DDS, although I did get TGA files to work using the method below. One online piece of advice was to grab a book art TGA from the game files, paste your own picture over it and save, but that didn't work for me.
Instead, I used a fan-made DDS editing utility called DXTBmp which, as the name implies, can convert between BMP and DDS/DXT. In the utility's Prefs menu, I selected Paint Shop Pro (any editor will do, ability to use layers a plus) as external editor.
Then I extracted a random book art file in DDS format from the file "Morrowind.bsa" using BSA Browser, in this case "boethiah_256.dds", which, like most book art files, is in DXT3 format, with an alpha buffer, and opened it in DXTBmp. From the menu, I chose Image, then Send to Editor, which opens the DDS as "norm.bmp" (ie. one layer, no alpha, no transparency) in the editor set under Prefs.
I then pasted the image over "norm.bmp". An easy way to do this is to copy the previously prepared image (in this case a screencap with the background whited out, then posterized to 3 bits to make it look more like a book illustration) and paste it into norm.bmp as a new layer, then delete the bottom layer. The image can be cropped to any size, a book art DDS doesn't have to be a multiple of 8x8 pixels like textures do. Having saved the BMP file, I loaded it back into DXTBmp by choosing Image, Reload after Edit. As the screencap below shows, the file still has its old alpha buffer.
So, the alpha buffer needs to be wiped clean. This is done by simply making a new one. I chose Alpha from the menu, then Create Alpha Channel (Green). This makes a new, blank alpha channel, deleting the old one. The choice for a green alpha channel was because the image contained little green, and much near-black, and with a black alpha channel, any completely black pixels become transparent, which was not my intention.
If I directly save the resulting picture in its old DDS format, then, presumably because I cropped it and pasted over it, the end result may be a skewed mess of pixels. In that case, I save it as 24-bit BMP, open the BMP again and save that as DXT3. Since that means I'm saving a BMP file directly to DXT3, and it works, why did I have to go through the process of loading a DDS, then having the program export it to another program as BMP and import it again? I've no idea, but that is what I had to do to make it work.
If I wanted a solid-coloured picture, I would be done now. But I want the area around the portrait to be transparent. Conveniently, that area is solid white, so if there are no other white pixels in the image, all I need to do is choose Alpha, Create Alpha from Colour, and choose that colour in the palette that appears. If there are white pixels in the image, I have to go back to the graphics editor to flood the to-be-transparent areas with another colour, one that isn't used by any part of the image, and base the alpha buffer on that colour instead. However! Since DXTBmp wants the alpha buffer image to be 256x256 pixels (because that was the size of the original DDS?), and will first make it that size and then rescale it to the image's actual size, the alpha buffer will have ragged edges unless the image is also 256x256. So I had to change the canvas size of that image (not resize it, as that would alter the proportions and make the outline fuzzy) back to 256x256.
To go all the way and see the transparence, I can choose Apply Alpha to Image, although this isn't needed for transparence in the game. It can be handy to make a smaller picture, though; make the alpha buffer at 256x256, apply to image and save image as TGA (to retain the alpha buffer), crop the TGA in the graphics editor, open it in DXTBmp again and save as DDS of type DXT3. Alternately, leave the picture at its full size and enter the desired size in the book's HTML code. It's also possible to simply use the TGA file instead of the DDS, but the file will be about four times as big.
The image is put into the book in the following way, where "IN" is a
subdirectory of "Data Files\Book Art" (note the forward slash!):
<IMG SRC="IN/mugshot01.dds" WIDTH="256" HEIGHT="256"><BR>
Since the picture was far too big, I scaled it down to half its size by using WIDTH="128" HEIGHT="128" instead.
For more on book HTML code, see Book text and tags.
| Back to technical issues |
The standard way clothing and armour work is like this: Morrowind NPCs have a segmented body, with every segment or "body part" (Chest, Groin, Upper Leg, Lower Leg andsoforth) having its own mesh. In case of segments that are paired limbs, the segment is further specified as Left Upper Leg or Right Upper arm, but the lower limbs are symmetrical whereas the upper limbs are not, ie. it's possible to wear a bracer on one arm but not on the other, but boots are always worn in pairs. The clothes are segmented using the same pattern. All segments are registered under Body Parts, with a mesh name, a segment type (the body part, but not specifying left or right) and a flag to say whether they are Skin, Clothes or Armour. So, if a NPC dons a shirt that uses the chest segment, this replaces the body chest segment. Armour goes over clothes, so if the NPC next puts on a cuirass, the shirt chest mesh is likewise replaced by the cuirass chest mesh. If a segment spot is no longer occupied by any wearable segment, the body segment pops back into view.
Since a wearable segment always replaces the equivalent body segment, all wearable Body Parts are further categorized by "Type" under Clothes or Armour, this Type roughly indicating what segment(s) they are supposed to be swapped out with. Wearables are registered not per segment, but as a collection of segments that is swapped out with a similar collection. For instance, the Clothes entry for the "shirt_chest", "shirt_right_sleeve" and "shirt_left_sleeve" segments could be named "common_shirt_example", of the type Shirt, ie. it displaces any other wearable of the type Shirt, and the three segments are assigned to it under the "Biped object" drop-downs, where the "Biped object" is the segment type assigned under Body Parts, plus a qualifier of Left or Right if applicable. In this way, long-sleeved shirts will displace the body's arm segments, while sleeveless shirts leave the arms alone. If no segments were assigned, wearing the shirt would simply displace any other shirt and leave a naked upper body (or a cuirass if the NPC was wearing that over the shirt). An exception is the unsegmented clothes that go over other clothes: the skirt and robe, even if not assigned their own segments, automatically make certain segments disappear, whether body or wearable. Skirts make the groin and upper legs invisible, so they should never be shorter than knee-length, while robes blank out the groin, chest, and upper limbs.
To make matters more confusing, the segment or "biped object" categories clearly refer to the bones in the mesh skeleton, but don't have to correspond to them. It is possible to define several body parts that all use the same NIF mesh file. To make this work, the mesh parts have to be correctly named internally. For instance, there is a mesh of an imperial cuirass (chest part and skirt part) combined with gauntlets, called "a_imperial_skins.nif". The imperial gauntlet body parts and the imperial cuirass body part both refer to this mesh. Internally, the parts of the mesh must have the same name as the Biped object, if they are to show up as that body part. So, while the gauntlets are remarkably complicated, consisting of various parts, their naming is fairly straightforward: the "header" or NiNode for the right gauntlet is called "Right Hand", and the bits of mesh or NiTriShapes are called "Tri Right Hand 0", "Tri Right Hand 1" andsoforth. The cuirass is more complicated: it has a Chest NiNode with two NiTriShapes for the chest and skirt part, and a Groin NiNode for the girdle above the hips, even though the skirt vertices are attached to the Pelvis bone, or Groin as it's called under Body parts. The skirt part of the cuirass has Chest in its name, rather than being called Groin and defined under Body Parts as a separate Groin segment, because otherwise it would make (the groin segment of) the greaves disappear rather than just hanging over them.
In NIF mesh files that don't serve more than one body part, the NiNode doesn't have to be named after a body segment, and is generally named after the wearables type (ie "Pants", "Shoes") or the mesh name. In fact, the NiNode can be called whatever you like. The NiTriShapes must always have a body part name and a number.
So, the situation is: Morrowind characters have ugly insectoid bodies made up of separately moving parts, and can't wear revealing clothing. Enter Better Bodies, final version: 2.2. Every humanoid race (there are separate beast race replacers based on the same meshes) now has one single mesh rather than a collection of little ones. Since, for all the leg objects, only one of the pair is needed, the other pair is reserved for clothes of the type that only partly cover the body segment, and shouldn't blank it out. So, each body mesh now has two right legs and feet; the Left equivalent of these body parts is defined but has no vertices, while the Right body part has the vertices of both (still attached to the correct bones in the skeleton, of course) as shown in the screencap below, with an added arrow pointing out the "Ankle" meshes, which take up the whole lower leg.
The Better Bodies documention phrases this as "freeing up slots" and lists
the following freed slots: Left Foot, Left Ankle, Left Knee and Left Upper Leg.
It also lists the Skirt, although that isn't a body segment, but a special type
of wearable. Each slot is meant to be used for a wearable Type that shows the
body mesh. To quote from the Better Bodies readme:
If you can use a slot that replaces a part, please do so, as this cuts down
on the total number of polygons. However, if you can't or don't want to replace
a body part, we've made four new slots available with Better Bodies. These slots
are "Left Foot", "Left Ankle", "Left Knee", and "Left Upper Leg." The following
guidelines should be used when using these slots:
On messing around a bit with the meshes of Better Bodies and its follow-up
mod, Better Clothes, I've found that the way to use these slots is 1. internally
name the NiTriShapes after the right slot, ie. "Left Ankle" and "Tri Left Ankle
0", and 2. use a mesh that contains the whole skeleton, so that there actually
is a left foot, left knee etc. even if they have no vertices. The game itself
uses the skirt slot for skirts, but since Better Bodies doesn't stop clothes of
the Skirt type from blanking the groin and thigh segment, revealing skirts have
to be "Pants". The way to make revealing skirts that go over pants is to either
make them part of a shirt, or assign the mesh and body part to the skirt slot
and make them Greaves. (NB. This is theory and has not been tested.)
| Back to technical issues |
NifSkope is a fan-made utility that can open NIF files and show their meshes and other data. It also allows the adding and deleting of blocks within NIF files and the editing of data within these blocks, such as the vertex values and UV coordinates. It can even export the vertices and UV maps of NIF meshes to a file in OBJ format, or import an OBJ file and have its vertices, faces and UV coordinates overwrite those of the NIF mesh block into which it is imported.
That is to say: NifSkope version 1.0.22 nicely exports to OBJ format. The later version that I use, 1.1.3 (build 36ebfdd), supposedly doesn't export to OBJ. It does in fact write an OBJ file, but with an excessive amount of numbers behind the decimal point. The older version wrote just six-figure decimals, which is the standard.
What both versions have in common is that they botch the UV map when
importing OBJ files. Here's why, and a laborious workaround: the OBJ format is a
3D format in ASCII form, so any OBJ file can be opened and edited in Notepad.
There are four basic types of line in an OBJ file: vertex data, starting with
"v", normals starting with "vn", UV coordinates starting with "vt" and faces
starting with "f". The first block is vertices, the last block faces, the order
of the two blocks between varies per 3D program. Additional info may be written
like the object's name and material, and if the 3D program supports object
groups, then every group will have its own four blocks of 3D data. If I open a
simple OBJ file, the first vertex line will look line this:
v -1.00000000 1.00000000 -1.00000000
and the first UV line like this:
vt 0.0000000e+0 0.33333333
and the first normals line like this:
vn -0.57735027 0.57735027 -0.57735027
and the first faces line like this (note that although vertices, normals and
UV coordinates are numbered starting at 0, when defining the faces they start at
1)
f 1/17/1 5/13/5 6/12/6 2/16/2
The three figures separated by slashes are the vertex number, the UV coordinate number and the normal number, which are usually but not always the same, since their three blocks don't have to be sorted in the same order. In this case, vertex 1 has UV coordinate 17, even though the OBJ is a simple cube with only 8 vertices. This is because of something called "unwrapping", where a three-dimensional object is cut at the seams and laid out in a flat shape, like a flayed skin. This only happens to the UV map, so several UV coordinates can belong to the same vertex: if every vertex had its own point on the UV map, this might be visible as a seam-like line in the 3D object, and/or extra faces might be needed. However, this is necessary, because NifSkope can't handle more UV coordinates to one vertex. The object will have to be remodelled and re-UV- mapped until there are as many vertices as there are UV coordinates. The seamy effect that happens when a line of vertices has exactly the same positions as another line of vertices with which it is not connected, can be counteracted by smoothing the normals of these vertices, by whatever means the 3D program allows.
But that's not enough. Even if there are now 17 vertices and 17 UV
coordinates, a face starting with "1/17/1" will throw NifSkope off, because it
expects the first number to apply to all three blocks. So, it wants "1/1/1", and
will in any case attach the first UV coordinate in the "vt" block to the first
vertex in the "v" block. (It will do the same with normals, but normals are more
likely to have the same order as vertices, while UV coordinates tend to get
sorted by their position.) As a result, the points in the imported UV map are
correct, but the lines that connect them are wrong. The only thing to fix this
is to edit the OBJ file by hand: find out from the "f" block what order the UV
coordinates should be in; for instance, the 17th "vt" line should be at position
1, the 13th at position 5, the 12th at position 6 andsoforth; it helps to add
the number at the start of the "vt" line. Then, using this information (and
keeping a backup of the original in case something turns out to be wrong),
reorder the lines in the UV block. Finally, edit the faces block so that every
"point" of a face - the three numbers separated by dashes - consists of the same
three numbers. Depending on the object's complexity, be prepared for a lot of
search'n'replace. After all this, the OBJ file may be imported and its UV map
will stay intact.
| Back to technical issues |
It is common knowledge that the global variable DaysPassed only exists, and is only kept up-to-date by the game engine, in installations that include Tribunal. Having created, on a new laptop, two side-by-side installations - plain Morrowind, and Morrowind plus both expansion packs - from the Elder Scrolls Anthology (as opposed to the Morrowind GOTY pack that I'd used to install the games to an older laptop), and added all of Bethesda's official plugins and the Unofficial Morrowind Patch, plus a few essential mods like legible signposts, I found that not only did the Siege at Firemoth plugin suddenly work without problems in both installations, but plain Morrowind had a global DaysPassed (seen by using "showvars" in the console) and it was being updated, too! I opened Morrowind.esm and the Unofficial Morrowind Patch and mods in the TESCS to see which one had added this global, but none of the data files displayed it. So how and in what file was it fixed? I'm baffled.
NB. Along with the global DaysPassed, some scripts use a local variable
daysPassed, which, by being declared local, hides and blocks the value of the
global variable from the script, because you can't have a local and a global
variable with the same name. Morrowind simply chooses the local over the global
and lets it slide, but OpenMW, up to version 0.39, seems
to have a problem with this, given that scripts using the local daysPassed (the
ones that make Maurrie and Nelos elope, and put Glathel in Gadayn's shop) are
not working.
| Back to technical issues |
Looking at the text in books and scrolls in the TESCS, it is clear that they use HTML tags to set font type and colour, include and size images, align elements, and organize the text in paragraphs. Attempting to use standard HTML in fan-made books also makes it clear that the HTML format used is not standard.
To start with a stupid mistake that I noticed after fixing the next two issues: I type something in Notepad and copy and paste it into the text field of a book I'm editing in the TESCS. Notepad puts unofficial line breaks in the text to make it wrap in the Notepad window; these line breaks are also copied and pasted into the text field. A line is now broken off in the middle.
This mistake persisted because I put the cursor at the end of the broken-off
line and used the Del key. This does not work. I have to go to the start of the
next line and use BackSpace.
And so, by accident, I found out that TESCS book HTML does not need standard
HTML line breaks. In a regular HTML text, lines have to be separated with
"<BR>" (break) or "<P>" (paragraph), or they all run together.
Although the last line in a book text must end on "<BR>", white
lines and new lines in the text field are displayed as they were typed, as if
using the "<PRE>" (preformatted) tag.
I discovered that a <BR> at the end of a line is superfluous. Putting
<BR> at the beginning of a line does have an effect, it breaks the line up
into "newline" and "text" and causes a white line to be displayed above the text
line. A line ending on <P> inserts an extra white line after it, while a
line starting with <P> has two extra white lines before it. Just
<BR> at the beginning of an otherwise empty line shows up as a white line,
<P> at the beginning of an empty line shows up as two white lines.
Next, fonts. I wanted some text in red to highlight it, and used the
following syntax:
I also wanted the red text to be centered. Now the simple TESCS HTML can't
understand tables, but does accept <DIV>, which I know as a text-dividing
tag used in code for really old browsers that don't understand tables either.
This tag goes at the beginning of a paragraph and can set properties for that
paragraph. For example, working from existing books, I came up with:
I found this out while working on a special scroll in which I wanted to
display globals set by a script. If the globals' values are set while the
scroll is opened, the game will crash, as I personally experienced time and time
again; the globals seem to sneakily update even when the script that updates
them is waiting for the scroll and/or menu to close. So I assigned another
script to the scroll, that displayed messages with the globals' values when the
script was equipped. In this script, using MessageBox, I put "%g" or "%f" as
placeholders in the message, and the variable names after the message. In the
scroll's own text field, I would have put the variable name directly in the
text, preceded by a special character. I have read that this special character
is the caret ("^"), but the standard books and scrolls use an ampersand ("%").
Both will work. (See also Standard display variables.)
Syntax is putting inverted comma's around all object IDs, using a comma after
each numerical argument, using capitals to mark the beginnings of words (ie.
"StartScript" instead of "startscript") and starting and ending each script with
"Begin Scriptname" and "End Scriptname". Lazy syntax is using inverted comma's
only around IDs with spaces or apostrophes in them, skipping the comma and the
capitals, and ending a script simply with "end". There is an argument for using
lazy syntax: less chances of making mistakes. The comma thing, for instance:

<FONT COLOR=FF0000>some text here</FONT>
More text here
but everything that came after the closing FONT tag, disappeared. After
trying various things, I concluded that closing tags not only fail to work, but
sabotage the rendering of any content following them. The only way to turn off
the red font was switch the colour to black with a second tag:
<FONT COLOR=FF0000>some text here
<FONT COLOR=000000>More text here
<DIV ALIGN="CENTER"><FONT COLOR="FF0000">some
text here</FONT>
<DIV ALIGN="LEFT">More text
and when the second line disappeared, I thought this was due to the DIV tag
and that only one DIV was allowed per document. It was the closing FONT tag that
was at fault, but I found that DIV wasn't necessary in my case, as the ALIGN tag
also works on its own:
<ALIGN="CENTER"><FONT COLOR="FF0000">some
text here
<ALIGN="LEFT"><FONT COLOR="000000">More text
Back to technical issues Silly mistakes
Syntax, and lazy syntax
Khinjarsi->PositionCell -172 -506 263 245 "Suran,
Desele's House of Earthly Delights"
is the same as:
"Khinjarsi"->PositionCell, -172, -506, 263, 245, "Suran, Desele's House of Earthly Delights"
just less typing, and no errors just because a comma was forgot or typed
double. And the cryptic errors (the script sometimes ran and sometimes didn't)
caused by a script ending on "End Begin Scriptname" (I'd copied and pasted the
starting line to the ending line) make a case for always ending a script with
just "End". For legibility, I do use capitals as in "StartScript" and "doOnce",
but not of course in "if" and "endif".
| Back to silly mistakes |
What I found out through lazy syntax: NPC names with hyphens in them also need to be enclosed in inverted commas. For instance, the following line in a script:
if ( player->GetDistance Zurgim_gro-Burug > 1024 )refuses to compile, saying that "Zurgim_gro-" doesn't exist. The same happens with Hleif_Strong-Arm, making a case for putting inverted commas around all Orc and Nord names. But Khajiit are affected, as well: "S'Bakha" can't do without them because of the apostrophe in his name, which similarly acts as a break. And a name starting with a number absolutely needs its quote marks, or the number in it will not be read at all. Using the spell 1gr_comp2_wb from Grumpy's companion script led to the error:
Could not find variable or function "gr_comp2_wb"This is why, in the original script, it's written like this:
if ( GetSpell "1gr_comp2_wb" == 0 )The above applies to NPC names, spell names, presumably cell names, and apparently variables, too; underscores in variables are fine, but they don't like hyphens.
| Back to silly mistakes |
If you create a new script, save it once before putting a StopScript command with its own name in it. Alternatively, create your MyNewScript with "StopScript MyNewScript" somewhere near the bottom, "save" (ie. compile) it once, get an error message that said script does not exist, save again, all is well. Or save the plugin containing the script while keeping the script window open (TESCS will not let the script window be closed without saving and compiling it) and then compile: again, all is well.
The TESCS compiler only sees the saved plugin file, not the changes made in the file while it's in RAM. That's why the following happened: I created a new spell and, in the same editing session, after adding and changing more spell names, added "AddSpell [new spell]" to an existing script. When I tried to save the script, the compiler claimed the spell didn't exist. I saved the whole data file (with the script window still open, so I didn't lose the script changes) and then reloaded the whole file. Then I opened and saved the script again, and it compiled.
Reloading the file after saving should not even be necessary, but in that case, it was. Maybe because I was still using the older vanilla Morrowind TESCS, or because I'd really been mucking about with spells.
| Back to silly mistakes |
An object, whether NPC or creature or anything else, cannot be referred to in a script unless it has been put in the game with the game editor and is loaded when the game starts, as anyone who makes a script to adjust the AI settings of Ranes Ienith can tell. The TESCS shows that there are 0 references of him in the game, and he is only dropped into it with a PlaceAtPC when his brother is attacked.
The only way to refer to such objects in a script is to place them in the game and then find a way to make them disappear until they are needed. From Tribunal onwards, they can be disabled in a user-made start script. In a Morrowind-only installation, which runs only the game's own startup script, statics and containers are best disabled by their own script, items are best inserted on-the-fly with the PlaceAtPC method, and unique NPCs, although they can clearly be inserted with the PlaceAtPC method, are best put in some hidden cell and then teleported to the right spot from a global script or dialogue box result field. Although using PositionCell to teleport a NPC into a cell where the player is will result in a crash if the player then addresses the NPC, because its dialogue hasn't been loaded yet, see Position and PositionCell: observations.
(I solved the Ranes problem by creating an ESM that puts him in the game, disabled, and changes all relevant scripts to enable rather than place him, then made whatever mods needed it, depend on this ESM.)
Something else that can't be referred to in scripts: new instances of persons. The first instance of a NPC, say Fargoth, may appear to be "fargoth" but is in fact "fargoth0000" (I don't know exactly how many zeros, it might be up to 8). If I create a new Fargoth in the console or in a script, that will be "fargoth0001". The next one will be number 2. Andsoforth. And these instances cannot be separately addressed in a script. I cannot, for instance, kill off both copies and keep only the original. Which is understandable since, like Ranes Ienith, these copies have not been placed in the game by the editor. I thought that a script can refer to instances that are placed in-game with the editor, like the many guard clones, but I've read that scripts can't refer to reference IDs (every clone, even if placed in the gameworld by the editor, is a reference ID), so the only way to do something with them is to have them run a targeted script on themselves somehow.
| Back to silly mistakes |
If an object ID referred to in a script doesn't exist or hasn't been placed in the game (or was created after the script was opened for editing but before saving the plugin file, see Script says it doesn't exist) then of course the script won't compile in the script editor. If such an object is referred to in the little script known as the dialog box's result field, I can only compile it with "Check error results", which, instead of giving an error message, shuts down TESCS. Fortunately, I had saved the file before running the check and so didn't lose any changes.
In this case, I used GetDistance on Aurane Frernis's three alchemical formulas. The first lies on a table/counter. The second is in a chest. The third is in her personal inventory. The second and third don't fully count as "placed in the game" since they are only in an inventory, therefore, I can't use GetDistance on them in a script.
| Back to silly mistakes |
It is possible to set the value of global variables in the dialogue window's result field. When the TESCS dialogue "Error check results" is run, all code in the result fields is carried out, possibly changing the value of the global variables in the plugin file; if so, they are not set back to their initial value and the plugin can be saved with a global having a starting value of, say, 5, which can produce strange results for scripts and replies that depend on it. The solution is to i. always check the plugin file's global variables and manually reset to 0 if needed before saving (don't alter any standard globals!) and ii. save before "Error check results", then run the check, then reload the plugin without saving.
| Back to silly mistakes |
Beware, beware of accidentally making two or more instances of a unique NPC. By working on a companion mod and loading an old game with an updated version of the mod, for instance, so that the companion who is already tailing you also appears anew in the deserted mansion where you're supposed to meet him. Two companions: twice the fun! Only, this companion had a number of external scripts running on him which now apparently ran on his clone, since he no longer kept himself fed and watered from his own inventory. Invisibly to the user, instances of any NPC are numbered; when a running global script refers to a NPC, apparently it takes the newest instance of that NPC.
So I disabled the clone with the console, opened the mod in TESCS, and added
if ( GetDisabled == 1 )
setdelete 1
return
endif
and then reloaded the game, hoping to be rid of the clone for good. The single
companion I now had, had the NPC's starting inventory; the game had considered
the older instance "disabled" while loading the newer one that I'd disabled with
the console! Either that, or the older instance had somehow been reset, losing
all the loot I'd given him to carry. (Or I'd been really stupid and disabled the
older instance, when I thought I'd clicked on the new one. They were identical
twins, after all.)
After that, teleporting the remaining NPC instance had the effect of disabling it. I quit, deleted the save and started a new game.
Since then, I've found another way of creating evil twins: make a mod that influences the Ienith brothers. Oops, Ranes Ienith can't be altered through an external script because he hasn't been placed in the game; he is created the moment the player enters the basement to kill them. So, in the mod, place him in the game. Now you can reference him in scripts. Next, make another mod which also references him in scripts and which also places him (in fact, copy the first mod, strip out what you don't need and add new stuff). If you add the second mod to an old game, the basement has already been loaded, complete with one single new NPC.
But if you start a new game where both mods contribute to the building of the basement, they will both put their instance of the NPC in it, and the scripts will only affect the first one, so while the first Ranes gives you a laid-back greeting, the second one will still attack. It is not (officially) possible to make the first mod depend on the second one. It is impossible (except maybe through a complicated startup script using a global variable present in both mods) to tell one mod to not load the NPC if another mod has already loaded it. The solution: make an ESM which does nothing but place the NPC in the cell. Then have both plugins depend on the ESM, by loading each plugin in TESCS while the ESM is ticked, and then saving it. See also Missing persons and scripts.
The case of the Ienith brothers is my own stupid fault, but the case of the twin companions is caused by what's called doubling: an object stored in a savegame is changed in or deleted from the game itself, and then put back, in its stored form, into the game after loading the save; if it was changed, the game now has two objects with the same reference ID. The doubling bug has been solved in the Morrowind Code Patch, and, depending on version, in OpenMW.
| Back to silly mistakes |
A stupid mistake that had me tearing my hair out trying to find it:
if ( GetJournalIndex <= 40 )
Note, no journal name. This was used in a result field for a response of a certain NPC. When that NPC responded, I had an EXPRESSION error message for that NPC's script. The NPC's script contained no errors, but any code in the result field is latched onto the NPC's script in-game.
Another stupid mistake in the result field:
if ( GetJournalIndex IN_Journal 40 )Note, no operator. "Error check results" does not report this. In the game, when the response with this code in the result field comes up, there is a ringing error sound, which shows that something is looping, until finally the game crashes.
Another one, in a script this time:
if ( GetJournalIndex Journal IN_Journal 40 )Note the extra "Journal", result of a careless copy/paste action. This gave me a "LeftEval" error when the script was run.
A non-journal result field error worth mentioning here: I used a global variable in the result field that had not been defined, ie. did not exist. So in the game, the line where I used this variable and all the code under it, was ignored. Strangely enough, "error check results" gave no warning.
| Back to silly mistakes |
On a modding forum, I found the method to remove unwanted parts of a plugin using TESCS: go to Files to open the dialog that loads a plugin, select that plugin and click on the button Details. A list appears of all additions the plugin contains; selecting an item on the list and pressing Delete puts an "I" ( Ignore) before that item, and when the plugin is saved, that item will be flushed out.
This method is mainly used to clean up dialogue. Each dialogue string has a string id, and a link to the id of the string before it, and the string after it. Inserting new dialogue "changes" the standard dialogue lines above and below it (the standard lines get an asterisk) by altering their links. Selecting and deleting these "changed" standard dialogue lines from the mod using TESCS will not stop the mod's added dialogue from loading, but will stop it from blocking the next mod's added dialogue. (On account of unnecessary before-after links, anyway. There are other ways in which one mod's dialogue can mess up another's, see Topics, the blue stuff.)
However, deleting one's own added dialogue lines can have unexpected results. I had added a number of strings somewhere in the middle of a Greetings section (Greetings 1, if I recall correctly), and to clean up the mod, set the standard strings above and below them to Ignore. I also set the topmost added line to Ignore, because I wanted to delete it, and saved the file. When I tested in the game, the expected greetings did not appear, and no wonder: after saving, which flushed out that topmost string connecting the rest of the strings to the standard strings above them, these strings had been relocated to the very bottom of that Greetings section. Lesson learned: always make sure that the top line of added dialogue links to standard dialogue before cleaning up the mod. (And that the standard dialogue line still has the same id with expansions installed, see Expansions and missing dialogue.)
Journal names are topics, and their entries are dialogue lines hanging under that journal topic. Journal entries are usually ordered from 0 to whatever their highest number is. But on examining mods, I found that sometimes, the order of journal entries is reversed. I can now guess why. When I export an added topic plus dialogue to file, completely delete that topic and all its strings with Ignore, then import that topic again from file - in other words, when the import creates a new topic in the dialogue database, rather than adding the strings to an existing one - the strings are imported in backwards order. This is not a problem with journals, but can be with topics for "spoken" dialogue, because the dialogue conditions are evaluated from the top down, with the most generic response at the bottom.
On the other hand, when responses under an existing topic are re-imported into a plugin, possibly after a spellcheck of the export file, and this is a standard topic that the plugin adds strings to, the standard strings above and below the added strings will be marked with an asterisk and added to the plugin as "changed", so the dialogue has to be cleaned up again as explained above. This happens with every import, so importing dialogue is a pain.
| Back to silly mistakes |
The following will never happen when simply adding dialogue to a Morrowind-only plugin in a Morrowind-only installation. I was making a Morrowind-only plugin in a GOTY installation using Wrye's GMST Vaccine ESP to prevent unwanted GMSTs in the plugin. Now Tribunal and Bloodmoon, as well as adding GMSTs, add a lot of dialogue, especially under generic topics like "someone in particular" and "specific place". My plugin also added dialogue under these two topics. And those under "specific place" never showed up.
I didn't understand why, as I'd hung both inserts somewhere under the topmost line in the dialogue present in the original Morrowind gamefile. With both expansions loaded, for "someone in particular", the dialogue was ordered like this: first a block of expansion dialogue, then the old dialogue. That's why my added dialogue for this topic did show up. For "specific place", the original and expansion dialogue was not so neatly separated, and the dialogue order of the lines, determined by the string ids that lines have or link to, had changed. That's how, although I'd inserted lines under an "old dialogue" line, they somehow ended up under an "expansion dialogue" line. The plugin relied on "Morrowind.esm" only, but the line id that my added lines were hung under did not exist in this ESM, hence, the lines were not shown.
Conclusion: when adding dialogue to a Morrowind-only plugin in a GOTY installation, make sure the dialogue is inserted under a line that has the same string id with or without expansions installed. I moved the added dialogue to just under Elone's reply about places the player should know about, and that solved the problem.
| Back to silly mistakes |
Sometimes I get a RightEval or LeftEval error message in the game from a script that compiled just fine in the editor. And I don't always get the same error.
The problem is with if-endif blocks that are too large. The compiler doesn't check for this, but if I have a huge if-elseif-elseif-etc-endif block that has about twenty elseifs, and the script finds the twentieth elseif to be true, and this is beyond the amount of code that the game can oversee in one block, the game suddenly realizes that this bit of code is "cut off", and the equation is no longer complete. It's like lopping off the end of a long sentence that doesn't fit on the line, and then realizing you can't find the period. If the first or second elseif is true, the rest of the block is not evaluated, and the error doesn't occur.
The solution is rewriting the block as smaller blocks, for instance, five if-elseif-etc-endif conditions, then, if one of these has been satisfied, a StopScript/return, then the next five if-elseif-etc-endif conditions, and so on.
| Back to silly mistakes |
GetDeadCount works like GetItemCount. The object to be counted comes after it. Yet I had the idea that it worked like GetHealth and had the object before it, with a prefix:
if ( yakov->GetDeadCount > 0 )This was at the top of a script, and all code under it was ignored. I soon discovered the correct syntax:
if ( GetDeadCount yakov > 0 )The funny thing is that the syntax with prefix was not reported as an error, either when compiling the script or when making the same mistake in the result field and then running Error Check Result.
| Back to silly mistakes |
The first way to crash the game through code in the dialogue result box is one that I could have seen coming. MSFD9 warns that two or more text displays in the same frame could cause a crash. I have the PC do something for an NPC. And the NPC says a big "thank you" and gives the PC a bundle of stuff. So far, so good. But because there is so much to receive, all the "XX has been added to your inventory" makes the dialogue window scroll upwards so fast that the big thank-you is completely lost from sight. So I split the response up into two parts and put "Choice "Continue" 1" under the first part. The idea is: text1->Continue->text2 and give stuff. But now I make a mistake: I put the code to give stuff in the result field of text1, not text2. So, while the dialogue window is waiting for me to click on "Continue", the messages about all the stuff I'm getting pop up in separate little windows. Then an error message pops up saying something about "menu pointer error", followed by a CTD. Note to self: always put code that alters the inventory in the result field of the dialogue line that comes after a "Continue".
The second way requires a script to dynamically generate choices. Normally, choice code is put in the result field and there are two or more choices. In this case, I want the player to be able to train a companion in all skills the game knows (that's an awful lot of choices) as long as the player has more points in that skill than the companion (that eliminates a few choices). So for every skill, I have to compare the player and the companion to determine if the choice will show up. For this, I write a script (something like CalculateChoices) which is run from the result field and shows the appropriate choices. When they've just met, the companion's skills will be poorly developed, hence the number of choices shown is so big it makes the dialogue window scroll upwards, and... CTD. The solution is to subdivide the choices: first have the player ask what attribute to work on, then what skill belonging to that attitude to train in. That means a crash-safe maximum of seven choices per question. Both sets of choices are presented by the same script, which also allows the companion to offer training to the player (again, only for skills that the companion is better at). Oooh, I'm so clever.
| Back to silly mistakes |
It was another one of those utterly befuddling recurring errors that had me tearing my hair out. I made a script comprehensively checking for indications that the PC had left a cell or at least travelled a long distance, to help manage companions. Not only that, but it checked if the PC was in any of the major cells (cities, shrines, points of interest) and if so, ran another script using StartScript to fill a default set of coordinates (like the silt strider port in Balmora) that following NPCs could teleport to if they'd lost track of the PC. The script got longer and longer with all these GetPCCells, and so I should not be surprised that the game crashed after a few cell changes.
Only, it wasn't the sheer size of the script, as chopping out a chunk of GetPCCells made no difference. The script that started the second script set an awful lot of globals, and maybe there was an upper limit to the amount of globals that could be set in one frame? As it was during the setting of place-related globals, at the end of the script, that crashes happened.
Only, not so much crashes, as freezes. I could teleport the PC with "coc" in the console, no problem. But when loading a new cell, especially via doorport, the screen went black and stayed black, sound looping, unresponsive to all keys. Of course "coc" would not have the same effect, as such teleporting may cause surrounding cells to not load (I have once found myself perched on a large green square in a sea of nothingness, but often neighbouring cells will load as you approach them). The freeze happened just as the screen indicated that a new cell was loading, and there had to be a connection.
It took several days of intensive testing to find that StartScript was the problem. If a script starts another script just as a cell loads, freezes may happen. Even if both scripts have code to wait while a menu is open (the black "loading" screen counts as an open menu).
To have one script lead to another, it's safer to use a polling mechanism, for instance, a global variable that is set by one script to be read by a second script, where the second script is a global script that runs non-stop. For instance, the first script does all its stuff and then sets variable "globalStartSecondScript" to 1. The second script begins with
assign localTempVar to globalStartSecondScript ; best use locals for expressions if ( localTempVar != 1 ) ; not yet? then wait. return ; keep cycling until the signal is given endif set globalStartSecondScript to 0 ; reset for next time [code to do stuff...]
Because scripts run only once per frame, and global variables aren't properly updated until the end of a frame, the script polling for the global variable won't "find it" until the frame after it is set. To make the second script run in the same frame (assuming that the second script doesn't come before the first script in the script queue anyway), the first script should set a local variable in the second script directly, using something like
set SecondScript.localTempVar to 1but since local variables need their script to be running when they're updated, and can be updated several times within one frame, unless the code is watertight, setting a remote local variable can go game-crashingly wrong; a global variable is the slower, but more reliable go-between.
In either case, the second script is not suddenly foisted on the CPU in the middle of a cell load, but is already part of the established script queue, and simply awaiting its turn.
| Back to silly mistakes |
The result field is the box under the dialogue response used for comments and scripted commands, like MessageBox. The syntax for the text-displaying command MessageBox is:
MessageBox "all the text you like"or:
MessageBox "text text text %g text %g" variable1 variable2where "%g" indicates each numerical variable to be inserted in the displayed string. Comments, on the other hand, are lines, or the end parts of lines, that begin with a semicolon:
; comment beginning at start of lineor:
set GlobalVarName to 2 ; comment to say that a global var is set to 2
The whole MessageBox line, ie. the command, variables and everything between quotes, must not exceed 512 bytes. If it is longer, and in a result field, and you run "Error check results", it causes a loop which can only be left by shutting down the TESCS with Ctrl-Alt-Delete; another reason to save the plugin before running "Error check results" (the other reasons being that "Error check results" changes global variables, and TESCS shuts down during the error check if any result field refers to missing objects).
Since a semicolon is always seen as the start of a comment, even in the quotes-enclosed text of a MessageBox line, using a semicolon in the MessageBox text causes a "quotes mismatch" error.
The variables that can be displayed are: in a script, global variables, or local variables from the script itself; in a result field, global variables, or local variables in the local script assigned to the NPC/creature addressed. In a result field, it is not possible to declare a new local variable, and no local variables from other scripts can be displayed.
As an example, I have a companion and ask him how he's doing. He replies something anodyne, but a MessageBox in the result field of his reply shows his real condition. I can't use GetHealth or GetLevel directly in a MessageBox line, and can't declare a variable like showHealth in the result field to fill with GetHealth and then display. I can, however, give the companion his own local script that declares a variable CompHealth and fills it using GetHealth. Since the code in the result field is tacked onto the local script of the companion, and has access to the local script's variables, I can put the following in a MessageBox line:
MessageBox "Health: %g" CompHealth
If I want to inquire about more than health, and the code needed gets too big and complicated for the result field (like if I wanted a list of stats and skills), I could write a script containing one or more MessageBox lines:
Begin HowAreYouScript
[code using GetHealth, GetParalysis, GetCommonDisease, GetLevel etc.]
if [high level and fine]
MessageBox "He's proud of achieving level X, and a health of %g" Comphealth
elseif [paralyzed]
MessageBox "He can't move! (BTW, health: %g)" CompHealth
elseif [ill]
MessageBox "He is too ill to care, and his health is %g" CompHealth
endif
StopScript HowAreYouScript ; if started, only run once!
End HowAreYouScript
and just call the script from the result field:
StartScript HowAreYouScriptThe script, called in the result field of a dialogue with the companion, is automatically targeted on the companion. Its content is not added to the companion's local script. It can't query the companion script's local variables, or display those variables without their object id. So the MessageBox no longer works.
Targeted scripts don't need a prefix for commands like GetHealth:
[companion-id]->GetHealth ; forget the companion-id bitbut they do need the id that a local variable belongs to:
[companion-id].CompHealth ; but do specify whose CompHealth this isso I tried adding the companion id to the message boxes, for instance:
MessageBox "He can't move! (BTW, health: %g)" [companion-id].CompHealthwhich also did not work. I thought maybe I should put quotes around the id:
MessageBox "He can't move! (BTW, health: %g)" "[companion-id]".CompHealthwhich displayed a button with the companion's id, because the quotes were interpreted as a button.
In short: MessageBox can't display remote local variables, meaning, local variables in a different script from the one that contains the MessageBox line. (More on variable types in Local, global, targeted, variables.) To display a companion's stats in MessageBoxes using a script like HowAreYouScript, I must declare variables in HowAreYouScript, maybe calling them "value1", "value2" and so on, fill these variables with the companion's stats (easy, since the script is targeted - I can even use it for other NPCs!) and use these variables for Messagebox:
short value1 ; display variable for MessageBox set value1 to ( GetHealth ) ; the generic approach, or: set value1 to [companion-id].CompHealth ; only works for this companion MessageBox "Health: %g" value1
According to MSFD9, global variables can be used (for AddItem, in their example, but also for MessageBox display) but not updated in a result field; that is, they can be assigned a new value, but when used or displayed, they still have the old value. This is consistent with the idea that a global variable's value isn't properly updated until the end of a frame. One caveat: assigning globals a new value does alter them before they are updated, and displaying them while they are being altered may crash things.
| Back to modding quirks |
There are a number of standard variables like %Name, %Class, %Race, %PCName, %PCRace. Using these in dialogue will see them replaced with the variable's value in the game. So "I am %Name, %Class" will appear in the game as "I am Yokel, Commoner" or "I am Fatcat, Merchant" depending on who says it.
The result field can also display text during a dialogue, using "MessageBox" and the text string; the text will appear in a different colour, and standard variables have the same name, but start with a caret instead of an ampersand. There don't seem to be as many standard variables for the result field; their equivalents should be ^Name, ^Class, ^Race, ^PCName, ^PCRace, but though I haven't tested them all, I can say that ^PCRace is not recognized. No race was displayed for my PC, just "^PCRace". Now this may have been because I didn't use the variable with MessageBox, but with Choice, another command that displays text from the result field.
As stated in MSFD9, ^Name will just display the value of ^PCName, so in result fields of dialogue for a specific NPC, the addressed NPC should be explicitly named, while in a script or generic dialogue result field, a paraphrase is needed ("your companion", "your slave"). (I believe the Morrowind Code Patch fixes this.) ^Cell applies only to the cell the player is in, and can't be used to locate companions gone missing. Since it is strictly for display, neither can it be used in the following way:
"npcname"->PositionCell 0 0 0 0 "^Cell"Too bad, eh. But see Position and PositionCell: observations below.
| Back to modding quirks |
Just as "Guard->SetHealth" may not do anything with multiple references of the NPC "Guard" in the game, so the inevitable multiple instances of the same door are hard to influence just using the door's name. But doors seem to be worse than NPCs in this respect.
If (discovered in the Suran Slave Market) four instances of the same basic door all have "opened by key X" in their reference data, then key X will only open the first of these doors. Even if I set the instances to be opened by four different keys X, X1, X2 and X3, only one these keys will open only the first door. On the other hand, the same key can be used for different doors with different IDs, but if used for different doors with the same ID, it only works for the first instance of that door ID. So three of the four doors had to be replaced by doors with unique IDs.
A door can't be (un)locked with an external script. If I use "lock door_x 50" in a result field, the game crashes; if I use it in a global or local script, the script aborts, and most or all of the code in it is not executed. (A Morrowind-only installation actually displays a message at startup that "reference door_x not found", while GOTY loads silently and the game either crashes or the script aborts silently, too.) A door can only be safely (un)locked from its own local script, so, to remotely (un)lock it, I let its script check for a global variable or journal entry.
| Back to modding quirks |
For testing purposes, I sometimes quickly kill off a NPC by opening the console and typing "npcname->SetHealth 0", or selecting the NPC by clicking on it with the console open, and typing "SetHealth 0". In the second case, "SetHealth 1" will also do the trick. It is not possible to kill NPCs by typing "npcname->SetHealth 0" in the console if they aren't in that cell and haven't been loaded by the game yet (npcname->ForceGreeting won't work either, although npcname->PositionCell will) because the NPC, not being loaded, can't respond to the zero health stat by dying. A script using GetHealth will report the NPC's health as 0 after the console command, but dialogue's Dead condition and GetDeadCount don't see the NPC as dead.
It is also not possible to remotely kill NPCs by using "npcname->SetHealth 0" in a script, in such a way that they will already be corpses when the player reaches them, or become dead according to the game. At least their health will be set to 0. If they are in the same cell as the player, they will even go through the motions of dying and become corpses. But their "dead count" will not be augmented, so if you use the "Dead" condition in dialogue, you won't get the right dialogue responses. I've tried this with an NPC whose health was set to 0 before he was disabled; even teleporting him to the cell where the PC was, so he could die properly before being disabled, didn't make him "dead" to others. So instead of the "Dead" condition, I used a "he's dead, folks," journal entry to get the right responses.
I think it's the "fight" situation that increments the GetDeadCount total. In one mod, I have to kill a bonelord to free some people. While testing, I am an unarmed level-1 character fresh out of Seyda Neen, so I bring up the console, click on the bonelord and "sethealth 0" just as it lets fly its first fireball. The script that includes a GetDeadCount of this monster reacts, and the story continues. However, if I use the console to kill it while it's still peacefully hovering around, before it sees me and engages in combat, GetDeadCount remains 0 and the story stalls for lack of a body count. So if the NPC is close enough - although I haven't tested this - using StartCombat and then SetHealth to 0 might just do the trick.
I've since read that GetDeadCount and the Dead condition are only changed once the NPC (or creature) has finished its dying animation. But if the NPC or creature is killed from the console while peaceful, it will go through the motions of dying and still not change either.
When killing a creature or NPC remotely for the sake of the corpse - so GetDeadCount and the Dead condition don't matter - it will not actually keel over and die until the player enters its cell. If it does so in a hidden space, the player doesn't have to see the dying animation, and the impression is that the corpse has been there for who knows how long. To get the same impression out in the open, it's better to have a separate, disabled and therefore hidden corpse where the creature/NPC should die (making sure the corpse has the same inventory as the living actor), and, using a script, disable the living actor and enable the corpse before the player reaches the cell.
| Back to modding quirks |
It should be obvious: you can't rest and levitate (or rest and swim) at the same time. So if you ask for a blessing at the Shrine of Daring, which confers levitation for a long period, you will not be able to sleep until the blessing wears off. You can't even use T to wait and make it wear off more quickly.
You can also lose companions. When teleporting by opening a door to another cell, if the player is levitating, any following NPC disappears; if the levitating player makes use of travel services, the following NPC may be left behind. The "Decius and Vorwoda" plugin addresses the door issue by replacing all doors in the game with "companion-friendly" doors which have to be clicked on twice. Without this feature, testing a mod can be a headache as you wonder where that companion went. Another solution is to have the companion stop following when you start levitating.
I found later that companions do follow a levitating player through the loaddoor into the cell, but can't materialize in the same spot as the player and so go to position 0, 0, 0 in that cell, which may be under the floor, or outside the rendered part of the cell altogether. They may then fall so far that they can no longer follow the player, since AIFollow becomes inactive if the distance to the followed actor is too great.
As an aside, this disappearing of levitating companions going through loaddoors has to do with follower scripts, particularly the much used and modified Grumpy's Companion Script, which makes the follower levitate when the player does. NPCs who are simply in AIFollow mode, and not levitating, survive doors whether the player levitates or not. I haven't looked into the companion-friendly doors, but assume they somehow stop the companions levitating until the teleport has finished.
When using T to wait in jail, I found that jail doors (or any non-teleporting doors?) are not good at keeping NPCs confined. I would rest for 24 hours before a prison cell door and find an Ordinator behind bars, while the jailbird I'd been watching was somewhere up the corridor. When putting NPCs behind bars, it's best to set their AIWander distance to 0.
| Back to modding quirks |
The best way to put a NPC somewhere is PositionCell. Unlike GetPos/SetPos, which set the coordinates within the cell the (N)PC is in, and don't seem to work over large distances, PositionCell states not only the coordinates but also the facing angle and the cell to which these arguments apply, all in one line. There is one disadvantage: unlike SetPos, PositionCell doesn't take variables. From Tribunal and upwards both are said to accept float variables, which must be local and not global variables, but when I transported Desele's dancing girls upstairs and back, storing their original positions in variables to use for their return, they always ended up near the door, which, I later found, corresponded to the coordinates 0, 0, 0. Not even SetAtStart could return them to their original positions, so I looked up their original coordinates and used these values with PositionCell in the teleporting script. Maybe, for PositionCell, "local variable" means it has to be a variable declared in the script of the NPC, rather than a local variable in a global script? (This has been tested by altering the slavescript and DOES NOT WORK.)
(NB. That SetPos does take variables as of Tribunal, is proven by the many companion mods in which the companion teleports to keep up with a speedy player character. That it doesn't take variables in plain Morrowind, is a bit of a setback for companion mods.)
PositionCell will work in cells that haven't been visited yet, and for NPCs that haven't been loaded yet. SetPos, on the other hand, only works if the NPC is loaded and in an active cell (ie. the cell the PC is in, and the preloaded surrounding cells; how many cells are preloaded depends on some game setting somewhere) or is still in the game's "memory" (where it stays for, by default, 72 game hours).
There is a potential problem with this. If a NPC is teleported, using PositionCell, to a cell the PC has yet to visit, the PC will enter the cell, the NPC will be loaded along with the cell, and all will be fine. But if a not-yet-loaded NPC is teleported to a cell where the PC is already standing, and this NPC has a script, the script will not run properly, because the NPC has been placed in the cell, but not loaded along with it. (This is solved in the Morrowind Code Patch.) And if this script is the famous NoLore-script or another script to do with dialogue, then if the PC addresses the NPC or vice versa, the game will crash.I had this problem when teleporting a few NPCs, including Fons Beren and m'Aiq, to Desele's House of Earthly Delights; they either showed up as black shadows, or looked normal but caused a CTD when greeted. Disabling and re-enabling the NPC sadly doesn't fix this. The two possible solutions are: have the NPC teleported to some hidden corner of the cell the PC is about to enter and brought out as necessary, or, but this is rather ugly, teleport the PC to the NPC's cell and both of them back to the cell where the NPC is supposed to appear. Blacking out the screen with FadeOut or a spell of blindness during this jump back and forth did not work.
I tried creating a custom unlit interior cell and using a script which teleports first the NPC and then the PC to that cell, then places them both in Desele's house. Since that unlit cell would be already in memory when summoning the next NPC, I made four unlit cells and scripted a different cell to be used for each summoning, using a global variable called DeseleRotation. However, since there were eight NPCs, which would have required eight cells, I scrapped the unlit cell approach and went back to teleporting the PC to the NPC's cell, but to coordinates in the ground/wall for the blinding effect. That was the best I could do, so I scrapped that mod until I could think of a better solution.
Using PositionCell from the result field instead of a script is supposed to crash the game. I wonder if this has anything to do with the problem of unloaded NPCs beng teleported in, because I haven't had crashes as long as I don't teleport "new" NPCs into the player's cell.
That I can use PositionCell on unloaded NPCs in faraway cells sometimes makes me forget what I can't do. I had three slaves teleported from Clutter Warehouse to an island on the Bitter Coast, all three paralyzed by adding the disease Witchwither to their inventories, and one hurt by using ModCurrentHealth -20. Unfortunately, neither AddSpell nor ModCurrentHealth had an effect, and I had to use AIWander settings (something else that does work on unloaded NPCs in faraway cells) to immobilize them. I then added code to the first greeting to add the disease and decrease in health.
I used to think that Position and PositionCell were for absolute coordinates - where PositionCell was only meant for interiors - while SetPos and GetPos were only for coordinates within cells. I was wrong on all counts. Firstly, both Position(Cell) and Get/SetPos report relative coordinates within interior cells. Secondly, all exterior cells are in one big grid with absolute coordinates. Outside, GetPos X/Y/Z return those absolute coordinates regardless of what cell I'm in, and "Position X Y Z angle", "PositionCell X Y Z angle cellname" and "SetPos x X, SetPos y Y, SetPos z Z" all put the object in the same location.
Moreover, when PositionCell is used for an outside location, it doesn't matter what cellname is used. I once used PositionCell with a nonexistent cellname and ended up in the heart of the Ashlands, which corresponded to "0 0 0" on the big grid; I don't remember what coordinates I used then, and whether I teleported from an indoor or outdoor location, but have since found out that, in the console, I can usePositionCell 0 0 0 0 "Vivec"and end up in that same Ashlands location, or
PositionCell 32870 -99470 1091 0 "Ashlands Region"to go straight to the Temple entrance in Vivec. Whether in the console or in scripts, it doesn't matter what goes in the cellname field, I can use "Wilderness", "MadeUpCellName" or even " ". I've tried putting these options in a script that teleports the player to various outdoor locations, using wrong or nonexistent cellnames, and it worked. If the game can't find an interior cell with the cell name given, it assumes the cell is exterior, and only pays attention to the coordinates.
Position should NEVER be used on NPCs. It messes up their rendering and collision detection to the point that you can walk through them, they don't have faces when seen from the front, and their teeth show through their cheeks when viewed from the side. Leading them into an interior cell, which has the effect of reloading them (I don't know if taking them into another exterior cell would have the same effect) fixes all this, but it looks scary. Always use PositionCell even if you're not sure of the cellname, because as long as the coordinates apply to an outside location, the cellname is ignored anyway.
Two quirks discovered while working on a companion. First: if, in a cell where the player is, a script uses PositionCell to teleport an NPC towards, for instance, a shopkeeper, and adds shopping items to the NPC's inventory to the point of overburdening it, the NPC snaps back to its old position. Teleporting and adding items in two different scripts would probably prevent that.
Second: if, again in a cell where the player is, a script makes a NPC cast a spell and then teleport with PositionCell, the NPC casts the spell, but PositionCell doesn't happen, as if the spellcasting cancelled it. I tried to make the NPC cast a spell with code in the result field, then use a script to teleport it; this caused a CTD. Finally I put the spellcasting back in the script and made the NPC wait 4 seconds (too short and the spellcasting cancels the teleporting, too long and it starts greeting the PC) before teleporting with PositionCell.
(Although I didn't implement it at the time, the second script should have included "SetHello 0" to prevent the greeting, and a way to check if the spellcasting animation was finished yet, and the spell effect expired, before the PositionCell was carried out.)
The fourth numerical argument for PositionCell is the Z angle, the direction in which the NPC faces. This seems not to work, but I've read that this is because solely with PositionCell and NPCs, not degrees but minutes are used. One degree is 60 minutes, so the number in degrees has to be multiplied by 60, or the rotation is too small to notice. PositionCell for the PC uses degrees, and Get/SetAngle for both PC and NPC also uses degrees. But simply using "SetAngle Z 180" on a NPC will not make the NPC turn round and face a different direction. The page on Tamriel Rebuilt that supplements MSFD9 has a different explanation; that GetAngle and SetAngle may use a different axis.
There is a command "Face x y", but if I use this on a NPC newly placed close to the player, the NPC will start to circle the player. And since PositionCell doesn't seem to take variables, I can't use those to change a NPC's facing direction either. If the NPC is not following and the PC approaches, the NPC will automatically swing around to face the PC (even if the NPC is lying down).
Lastly, using PositionCell in the console to move a NPC, who was standing next to the player somewhere on the Bitter Coast, to Balmora, caused a CTD. Which is funny, since companions can be teleported away from the player by any means without problems.
| Back to modding quirks |
GetArmorType won't work for NPCs, in either a script or the result field. Correction: I've found it will work in a script, if I put the value returned by GetArmorType in a (local) variable and use this variable where needed. It is not possible to define local variables in the result field, and using global variables would be tricky, so I opted to run a script from the result field instead. That's how I found out that if the first GetArmorType call in the script is for the helmet ("GetArmortype 0") no value is returned, possibly because of the 0. If I start with "GetArmorType 1" or any value higher than 0, and then use "GetArmorType 0" to check for the helmet, it does work. I know that GetArmorType only works since Tribunal, but this was discovered in a GOTY installation with the latest patch applied.
Companions (NPCs in "follow" mode) are like an extension of the player. They will not speak out when the player commits a crime, and when they attack something, the player bears the blame. (And gets the experience points?) But companions also somehow inherit the player's armour skills. If I give a slave bracer to a companion to carry, he will not auto-equip it, as it is technically heavy armour, and his highest armour skill is Light Armor. But when I play as an Orc and have a high Heavy Armor skill, my companion, with his higher Light Armor skill, nevertheless equips the slave bracer. Perhaps useful information for modders who want to make a Morrowind-only companion, as Morrowind-only NPCs can't be told to equip something. Although this is a GOTY-requiring companion, so I can't test what he does under Morrowind alone.
Although a piece of armour has "health" just like a living actor, I can't "damage" a piece of armour with ModCurrentHealth, ModHealth or SetHealth, either as a command from a script or result field, or in a script attached to that item. Since the script would act directly on any reference of the item in the game, I deduce that these commands simply don't work for armour (and probably weapons). I can only "damage" a piece of armour by placing it in the game with the editor, and then editing its reference data.
| Back to modding quirks |
Code in the result field is executed immediately, while the dialogue menu is still open. If sounds are played, they are played straight away, all at the same time. SetPos or PositionCell are executed immediately, which may cause crashes if the object being teleported was doing something else. Objects are added to or removed from the inventory with AddItem and RemoveItem instantly, and Journal just as instantly adds journal entries.
This mainly matters for its effect on dialogue topics. I could take the commands from the result field and put them in a script called DoStuffNow, and put "StartScript DoStuffNow" in the result field instead. Now, any teleporting will appear not to happen until the dialogue window is closed, and even though the dialogue will say "Your journal has been updated", if the journal update affects the available topics, the topics will also not be updated until I click on them or close and reopen the dialogue menu; a now obsolete topic will instead get a reply like "I don't trust you enough to tell you about that". I haven't tested what happens with topics that depend on items being in the player's inventory, but conclude that any action which should update topics belongs in the result field, not in a script.
Although, when a script is run from the result field, its actions (teleporting, highlighting or deactivating topics) will only become visible after the dialogue window is closed, the script itself does run instantly when its result field's topic is clicked, without waiting for the dialogue to end, which is annoying if, for instance, that script sets variable values which are not meant to change while the characters are "talking". The order doesn't matter, both:
Goodbye set INPCPositionCell to 1 StartScript INPCPositionCellScriptand:
set INPCPositionCell to 1 StartScript INPCPositionCellScript Goodbyedon't make the script wait until the talking is done. Now, the seasoned modder may say: just add a "menu is open" condition in the script, as below:
if ( MenuMode == 1 )
return
endif
which will constantly make the script loop back to its first line until not just
the dialogue window, but all menus are closed. I did this and it didn't work,
not even after recompiling all scripts, because the script was run under a
Greeting. Apparently, a Greeting (as opposed to a line of dialogue hanging under
a topic) kicks in before MenuMode is set to 1. I had to create a delay with a
local variable, which makes the script wait four milliseconds and then turns
itself off until the script finishes:
Begin NPCPositionCellScript
; to teleport PC from dialogue only after dialogue window has closed
; and other post-goodbye stuff
short waitabit
; since Greeting happens too fast to set MenuMode:
if ( waitabit < 5 )
set waitabit to ( waitabit + 1 )
return
endif
set waitabit to 10 ; will be reset to 0 at bottom of script
; now the script responds to MenuMode
if ( MenuMode == 1 )
return
endif
[all the code that should happen after closing the dialogue window]
End NPCPositionCellScript
| Back to modding quirks |
Once, I needed a damaged helmet to materialize on a counter. There was no way to place and then damage it while the game was running, so I had to put it on the counter damaged in TESCS, disable it in a startup script, and enable it when needed.
Much later, I needed three books to appear in Thieves Guild halls as part of a quest, in a mod that should work in Morrowind only, so I couldn't add startup scripts. I tried to put them in the game and have their internal script disable them, then poll for a certain journal entry to enable them. Their polling took so much time that it slowed the game. It would be better not to create them unless/until needed, and the simplest way to put an object in the game is to either add it to someone's inventory, or use PlaceAtPC which creates an instance of it near the PC. But how to teleport them to the place where they're supposed to be? I decided to attach a script containing a one-time PositionCell command to the books. They teleported themselves so promptly that I didn't even have time to bend down and see if they were lying at my feet. So I decided that if I ever needed to pop an item into the game, I would use PlaceAtPC and let the item's script take it to where it was needed.
For the same plugin, I also needed three crates of moon sugar to appear on a dock. I tried the PlaceAtPC method again, but for some reason (containers don't like being teleported?) it didn't work. So I used the helmet trick again: pre-placed and disabled them, although, still not being able to do this in a startup script, I had to let them disable themselves and then enable themselves at the right time by polling for a journal entry again. For some reason, this worked well with the crates. Maybe it was because the crates were in a thinly populated exterior, while the books were in interior cells with plenty of NPCs.
The crates were less stable in vanilla Morrowind than in GOTY. When I placed them in the console, I got memory pointer errors and a message "Trying to Runfunction .. greater than index count". When I emptied them, which is the cue for their own scripts to make them disappear, they didn't; when I left the cell and then revisited it later, they did disappear, but with an error message.
| Back to modding quirks |
A page with scripting tips related to Josh's Farmer Mod says that global variables sometimes update too slowly to be usable in scripts - which I've found to be true - and that another way to quickly test for a condition is to add lights (not candles or torches, but actual lights) to an inventory, as they can be tested for with GetItemCount, but won't show up in the inventory.
I thought I would use this principle for a Morrowind-only quest in which, depending on my actions, two groups of people would be behind bars for a specified number of days. A script would count the days and position them back in their old spot once they'd done their time. It couldn't be a global script, because then it would not restart after saving and reloading. It couldn't be a local script, because I didn't want to tamper with the NPCs' scripts. Besides, it had to keep running and counting as long as the player was alive. I hit on the idea of putting the script in a new kind of light and adding this light to the player's inventory, where it would do its counting invisibly and then remove itself from the inventory, not in its own script because that has been shown to cause a CTD, but from another little script that it would call. That's how I discovered two things: first, using an item's script to delete that item from inventory, but via an external script, will still make the game crash, and second, the lights method only works for NPCs, and I'll explain in the next paragraph why. I ended up using a global script which does stop running when the game is saved and reloaded, but which can be restarted by addressing the jailed NPCs.
If a light is added to the player's inventory, it will be invisible if it has no icon art, but a cryptic message will appear: " has been added to your inventory". A light has a togglebox saying whether it can be carried. If this toggle is off, it's impossible to name the light. I checked the togglebox and called the light "A mission" so the message would be "A mission has been added to your inventory". But then I got a default "missing art" icon in my inventory! The light doesn't have a mesh either, so if I dropped it, I could never pick it up again. Incidentally, if its own script makes it drop itself from my inventory, this counts as deletion and crashes the game. So if I put lights in the player's inventory, either to keep a script running or as an alternative to a global variable, I will get either unwanted messages or unwanted icons, and the risk of littering the game with invisible objects. If used for scripts, the lights also risk littering the player's inventory, as there is no safe way to delete them.
Adding lights to a non-standard (ie. not running "slaveScript") slave's inventory to show his "freed" status, I also found that even when the "carryable" setting is off, the slave will still carry the light when it gets dark, meaning, the slave's arm will take the carrying position despite not holding anything visible, and light will reflect off the slave. So I stored the slave's status in a global variable.
Summarized: lights are fine for transferring the value of local script variables (which can't always be queried directly) in short interactions, but should only be added to inventories of NPCs, deleted from those inventories when no longer needed, and never have a script running on them.
| Back to modding quirks |
Making topics appear blue on time, so that they can be clicked on: I ask Fargoth about "(The imperials have) taken everything", which sets my journal to 10, and he tells me he would die for some nix-hound meat, which reply leaves a line in my journal, setting it to index 20. The topic "nix-hound meat" is programmed to turn blue at journal index 20, but doesn't. This is because the journal index is set to 20 after the dialogue is displayed, and the dialogue is not updated to make an already displayed phrase turn blue. To keep the conversation flowing smoothly, I have to program the topic "nix-hound meat" to appear even before the journal index goes to 20, in order to make it light up in blue when Fargoth mentions it. If another character has mentioned it previously, it will now appear as a topic in the topic column even before Fargoth brings it up, but fortunately he is the only one who does.
(This is why, in standard dialogue, newly unlocked topics are often introduced in a Greeting.)
The dialogue is updated to change blue lettering back to normal lettering when a topic is no longer valid, for instance, if I bring Ajira the flowers she wants while she tells me to go to lake Amaya, my chances of finding out more about lake Amaya are shot as the letters lose their colour.
See also Straight from the result field for the effect on topic processing of letting a script add the journal entry.
If one plugin (ESP file) has a certain topic, say "skills", and another plugin has that same topic "skills" (though obviously with different lines) and that topic does not exist in an ESM (master) file, then the topic will only have the lines of the last-loaded plugin. This means that a topic may no longer turn blue when you expect it to, because it has been superseded by a newer version of that topic with new conditions for when the topic is supposed to be blue. The solution is said to be using "AddTopic skills" in the result field to at least make the topic appear in the topic-listing sidebar, although whether this will produce the desired response is another matter. AddTopic can also be used in Fargoth's example to make "nix-hound meat" appear in the topics list before it turns blue, where hopefully the player notices it being added. Which may not be the case if the list is already quite long and the topic starts with a letter way at the back of the alphabet.
Speaking of modders' topics, an informal standard set by the "Got The Time" plugin is to have completely generic but often used topics start with a double dash, to place them above all other topics. I decided to adopt this standard with the "Coordinates please" plugin.
Related to blue (clickable, unlocked) topics: if a topic's condition is, say, a journal entry having appeared or a global variable having been set, then as long as this condition is not met, the topic will simply not be active. However, if a topic's only condition is Disposition, meaning, it should only appear when a NPC likes the PC well enough, the topic will always be active, but until the disposition is at the right number, the NPC will say things like "I don't like you well enough to talk about that". So to make a topic rely on disposition yet keep it absolutely hidden until the disposition is right, extra measures are required.
| Back to modding quirks |
The dialogue editing screen allows me to add responses to a topic for everyone (all speaker fields empty), or for NPCs of a certain race or class or other group (choose race/class/etc. from drop-down boxes). Or I can choose an actual NPC ID, which makes the race/class/etc. choices moot, so, if these were already filled in before I chose the NPC ID, they become greyed out. However, they are still in effect!
I added a response for the class Slave, then filled in a NPC id, which greyed out the Class field, of a NPC who was not a slave. Consequently, that response didn't appear in the game. I had to go back to that response, set the NPC id to "blank", then set the now editable class field to "blank", then fill in the right NPC id again. In this case my mistake was to leave a value in the Class field before filling in the NPC id, but no doubt the same applies to the other speaker fields.
| Back to modding quirks |
There are three kinds of scripts: local, global, targeted.
A local script belongs to an actor or other object and runs as long as that actor/object is loaded, ie. in the same cell as the player, or an adjacent cell; it can't be stopped with "StopScript". It is assigned to the actor/object in TESCS. Because each frame processes all loaded game objects, and several game objects can have the same local script, a local script can run many times in one frame; CPU-heavy local scripts can slow the game.
A global script is not attached to any actor or object, so there is no default target, as it were, to apply commands to. It must be started with StartScript (or, from Tribunal onwards, be designated a start script in TESCS), after which it runs non-stop no matter where the PC is, until it is stopped with "StopScript".
All scripts run once per frame.
A targeted script is a global script that, when started, is attached to an object or actor, like an ad rem local script. If the object or actor is not loaded in memory, the targeted script loses focus (is no longer attached to that target) and continues to run as a global script, until stopped. To make a global script run as a targeted script, it must be started with a prefix:
Actor/Object-id->StartScript TargetedScriptbut a script started from the result field in a dialogue window is automatically targeted on the actor being addressed. Being a global script that runs once per frame, a targeted script can be attached to only one game object at a time, although a game object may have more than one targeted script attached to it. While attached, the script can access the game object's properties with functions like GetHealth, but it can't access the local variables of another script running on the same object, whether that other script is targeted or local.
Case study: I use a script, StayWithCrassius, to have my companion stay with Crassius Curio for a while. The script tells the companion to stop following me and go stand next to Uncle Crassius. It also fills his health and magicka bars to their original value, which I know because that value has been stored in variables in the companion's local script. The following attempts to set his health do not work:
SetHealth mycompanion.basehealthThe targeted script can't read the variable "basehealth" in mycompanion's local script. Putting this in a result field, and the error check will complain that SetHealth (and by extension any Set/Mod command) can only take local variables. So I add local variable "tempval" to the script:
short tempval set tempval to mycompanion.basehealth SetHealth tempvalThe script compiles in the editor, but won't work in the game. Maybe because it doesn't know whose health to set? I add a prefix:
short tempval set tempval to mycompanion.basehealth mycompanion->SetHealth tempvalThis produces an expression error of the LeftEval type when the script is run in the game. The script is targeted on Crassius Curio by default, maybe if I target it on the companion when starting it from the result field?
mycompanion->StartScript StayWithCrassiusThis seems to work, but what I forgot while testing is that actors can independently regenerate health over time. At least it doesn't cause errors. The "mycompanion->" bit in "mycompanion->SetHealth tempval" is now optional, but the "mycompanion." qualifier is not; the compiler won't complain if it's missing, but the game will. And neither is the tempval variable; Set/Mod commands don't like directly being assigned remote variables, presumbly because remote variables can't reliably be read.
A safer way to handle the above is to either use a global variable to pass the value of "basehealth" from the local to the targeted script, or, specifically for health, ignore the basehealth variable and use GetHealthGetRatio to calculate what the companion's full health would be, then use ModCurrentHealth to raise it to that number. Or skip the health check and use a huge number like "ModCurrentHealth 9999", as ModCurrentHealth can't raise health past its maximum anyway.
The addenda/errata page of Tamriel
Rebuilt weighs in on "Referencing variables on other objects and scripts":
I looked up the script FraldCounter, but it doesn't use the remote variable
to update a script variable; the remote variable is compared to a number in an
if-endif block. I don't know if that makes a difference. Also, is the "final
version of the game" the one in the Elder Scrolls Anthology, which also fixes
other bugs still present in the GOTY edition? Because I doubt anyone bought the
anthology just to update GOTY. In short, when making a mod, never assume a
script can read another script's variables without thorough testing in a
non-final game version. At this point I should mention that there are also three types of variables:
global, local and remote. Actually, there are just two types of variable:
global, defined separately in TESCS, and local, defined in a script, whether the
script is local or global. So, a global script can have local variables, and a
local script can use global variables. A remote variable is any local variable
in another script, for instance, "basehealth" is a local variable in
MyCompanionLocalScript, and therefore a remote variable to StayWithCrassius.
Remote variables can be set, but not necessarily read. All variables have an
initial value of 0. (Except for the standard display
variables, which are filled with strings by the game engine, and can't be
set through scripts.) Global variables, "globals" for short, are accessible by all scripts, and
always keep their value. (They are used to store the time and date, for
instance.) They may be altered several times in a frame, but their change in
value is not finalized until the frame has finished processing. Since they can
be altered from everywhere, I assume this is to ensure consistency. This can
make them too slow for tasks like adjusting dialogue responses. Local variables are the variables defined at the top of any script. Their
scope is limited to the script they were defined in; two scripts can both have a
variable with the same name, but each variable can be read only by its own
script. So variable "doOnce" in ScriptOne may be 0, while variable "doOnce" in
ScriptTwo may be 1. Setting this variable in the first script does not affect
the variable with the same name in the second: to set that variable in the
second script, the first script must use a qualifier:
The reported limitation to writing remote variables into another one - "Note
that the reverse does not work: Set local_variable to MyObject.variable ;this
doesn't work!" - is incorrect in the final version of the game. See the vanilla
script "FraldCounter", which does update the variable.
set ScriptTwo.doOnce to 1
A script doesn't need a qualifier for its own local variables.
(A warning from MSFD9: if a script contains a local variable defined in another script, but not in itself, the TESCS compiler doesn't report this error, and the script malfunctions in the game. Because I use the same variable names in various scripts, I don't get a warning if I use ThisLocalVar in a new script that has no "short ThisLocalVar" at the top. This had me tearing my hair out and cussing until the penny dropped.)
Local variables in local scripts always keep their value. A NPC's local script may be used to store information (like level and stats) that a NPC loses when being "reset" when the player has been away for too long (basically, when the NPC is flushed out from memory, to be reloaded from its physically stored data next time it's encountered). Local variables in global scripts (whether targeted or not) retain their value as long as the script runs, which is generally throughout the game if the script is not stopped with StopScript.
It's good scripting practice to put the value of a global variable into a local variable (I use "tempval" and "tempfloat" a lot), and use that local variable for any calculations, assigning the global variable's value to the local variable at the end if needed. Global and local variables don't like to be mixed, and using them together in calculations can cause strange LeftEval and RightEval error messages.
Trying to make an expression work too hard also causes LeftEval and RightEval errors, and I'm not always sure what causes them, too many arguments or the arguments being mixed local/global variables. This caused an EXPRESSION error, from too many arguments or from trying to read remote variables?
set tempval to ( tempval + ThisActor.localvar1 + ThisActor.localvar2 )This causes a LeftEval:
if ( ThisActor.localvar - AGlobalVar <= 0 )And this causes a RightEval:
if ( ThisActor.localvar <= AGlobalVar )But if I change the second example to
set tempval to AGlobalVar if ( ThisActor.localvar <= tempval )it works. As the examples suggest, they are from a targeted script using a global variable, a NPC script's local variable, and its own local variable tempval to make them cooperate. In a local script, the following:
if ( localvar - AGlobalVar <= 0 )can also cause problems, so it's best to combine the two values in another local variable, which I'm again calling tempval, before proceeding:
set tempval to ( localvar - AGlobalVar ) if ( tempval <= 0 )
Back to the Crassius Curio case; I thought I could read a remote variable from the companion's local script in StayWithCrassius, but I couldn't read a remote variable in StayWithCrassius with another script. So, this works:
set StayWithCrassius.ALocalVar to tempvaland this, where I try to read a remote variable's value to raise it by a certain amount, doesn't:
set StayWithCrassius.ALocalVar to ( StayWithCrassius.ALocalVar + 1 ) ; or set tempval to StayWithCrassius.ALocalVar set StayWithCrassius.ALocalVar to ( tempval + 1 )The best way to handle this is to have StayWithCrassius maintain a global variable to store ALocalVar's value in. If the other script is a global script that runs throughout the game, StayWithCrassius can also fill a local variable in that script with its ALocalVar value beforehand.
Warning: for a script to set a variable in another script, that other script has to be running! I caused a nasty game crash by having a global script constantly update a companion's local script, which worked fine until the player left the companion behind in another cell. If the global script is targeted, leaving the target behind may make the script lose focus. In both cases, the game crashes.
Would the same happen if I set a variable in a global script that's not running? I've found that the following code in the result field won't work (but causes no crash):
StartScript AGlobalScript ; automatically targeted since run from dialogue set AGlobalScript.localvar to 2presumably because the script doesn't start to run until the next frame after it's called.
To see if a global script targeted on an actor still has a target, or if it's gone off the rails, the script should begin with a focus test, using this code snippet straight out of MSFD9:
if ( GetHealth == 0 ) ; note, no prefix
return
endif
because when Health equals 0, the actor is dead, or the script has lost
focus.
To assign a variable in a global script, it should first be tested if the script is running:
if ( ScriptRunning TheGlobalScript == 1 )
set TheGlobalScript.localvar to AValue
endif
Local scripts don't work with ScriptRunning; they run when the game object they're assigned to is in a loaded cell, which typically means, the game object is where the player is. So it's safest to set a variable in a local script when the player is next to the local script's object or actor, for instance, through a dialogue window's result field when talking to the actor. For actors that can't be addressed because they are not in sight, direct proof that their local script is running is that a scripted ForceGreeting can open a dialogue window. Indirect and less conclusive proof would be that a targeted script running on them hasn't lost focus yet.
Sometimes it will seem as if global and local variables don't work together when, in reality, a function simply won't accept a type of variable (like SetPos, which won't accept globals) or any variable at all. In my experience, AddItem and RemoveItem will not accept variables. This is annoying when trying to empty an inventory of, say, all arrows: it is possible to use GetItemCount to put the number of arrows in a variable, but it is not possible to use RemoveItem with that variable to have that many arrows removed. The solution is to remove arrows in a loop, one at a time, either checking with GetItemCount in each iteration whether all the arrows are gone yet, or putting the total number of arrows in ALocalVar and using a "while" loop (which is said to be slow) to delete one arrow per iteration, decreasing ALocalVar by 1, until ALocalVar is 0.
Perusing MSFD9 turned up the following on page 37, under "Notes on using
Additem / Removeitem in dialogue":
These functions can accept global variables, but only in dialogue results, and only if you don’t set the global variable in the same dialog result (Forum info / Argent; According to Argent, the maximum amount he has been able to add using AddItem, var was 65534 (using a long var=2147483520)). In addition to Argent's info about AddItem/RemoveItem accepting variables in dialog action boxes: make sure that the variable in question doesn't change in the same box.
So RemoveItem would work with a global variable in dialogue, as long as I set that global with GetItemCount before the relevant line of dialogue.
What is funny about accessing remote local variables is that, without targeting, you can set their values but not get their values, but sometimes, strangely enough, you can't even set them! But that may be a matter of brackets. These two lines of code in a script targeted on an NPC which has its own script and local variables, will work:
set companion.INBintelligence to ( companion->GetIntelligence ) set companion.INBathletics to companion.realathleticsBut this will not:
short LocalVarIntelligence set companion.INBintelligence to ( LocalVarIntelligence )because of the brackets around the variable name. And I suspect that
set companion.INBathletics to ( companion.realathletics )wouldn't work either. Normally, brackets are required, as in
set companion.INBathletics to ( player->GetAthletics ) set companion.INBathletics to ( tempval + 4 )although the game is forgiving about a lack of brackets; far less so, however, about brackets with no space between the bracket and the expression within, eg. "(player->GetAthletics)".
| Back to modding quirks |
This is a continuation of Local, global, targeted, variables, but was given its own header because that block is already quite large. Most slaves in Morrowind have a local script called "slaveScript" which keeps track of whether they are owned or freed. The slave called Yakov (found at the Suran Slave Market) does not. So I made a script called YakovSlaveScript and ran it targeted on Yakov. That's how I discovered three things:
1. There are special local variables that can be defined in an NPC's local script to give that NPC certain abilities: nolore, companion, stayOutside. They can't be defined in a targeted script. Well, they can, but they'll have no effect.
2. And, of course, if you define a local variable like slavestatus in the targeted script and then make a response in the dialogue editor depend on that variable, it won't work either because dialogue responses can only depend on local variables in the local script of the NPC being interacted with. Moreover, the variable can't be used in the result field. I tried something like "MessageBox "Slavestatus %g" YakovSlaveScript.slavestatus", without success.
3. And, even more importantly, local variables from a targeted script can't be queried by another targeted script. Makes sense, as both scripts are targeted on an object (which does not have these local variables) and not on each other. The other targeted script could do something with, for instance, "YakovSlaveScript.nolore", but that way, it affects the script and not the individual NPC (as opposed to the slaves with "slaveScript", who each have their own instance of this variable). Not that it matters, as a targeted script is still a global script and can be run on only one game object at a time, ie. there would be no other YakovSlaveScript running.
In vainly trying to give Yakov normal slave features (other than by adding the slavescript to the NPC, a change that might be made undone by a newer plugin changing, say, his appearance or abilities) I discovered, in an effort to add choices to slave dialogue through a script that displayed the same choice options after every response, what is mentioned in Local, global, targeted, variables: local variables in a local script can only be queried by a targeted script if the actor that the local script runs on, is specified.
As an example, say I add locall variables companion and stayOutside to "slaveScript", which means every slave can now carry my stuff and be told to wait outside. I then add a dialogue topic "follow" with a response for Khajiit slaves (the "third person" people) and other slaves. To avoid having to put the same list of choices - including: "Follow, but stay outside" - in the result fields of both responses, I stick them in a script called "SlaveFollowChoices", which terminates itself as soon as it has displayed the choices, and put "StartScript SlaveFollowChoices" in both result fields, which means the script is targeted on the NPC who engages in dialogue. This script is run for every slave with whom the PC talks about "follow" and so it uses the slave's local variable stayOutside (added to the enhanced slavescript) without identification:
if ( stayOutside == 1 )instead of
if ( menelras.stayOutside == 1 )since it should work for every slave. It won't. When the SlaveFollowChoices script encounters this remote local variable, it stops running. I had to ditch the script and put the choices directly in the result fields instead.
Most special local variables, particularly stayOutside, were added in Tribunal. Another local variable added in Tribunal, specifically for Calvus Horatius, a mercenary that the player can hire, is minimumProfit. This variable keeps track of everything the companion NPC carries - both possessions and money - that wasn't part of the companion's base equipment. Hm, so if I created a companion with no money or possessions, but with "minimumProfit" defined in its script, this would work like the function "PC Clothes Modifier" which returns the value of what the player is wearing, and so is zero when the player is naked. It wouldn't quite work because minimumProfit also counts non-wearables and money; still, maybe this could be an easy way to check for dialogue options like "PLEASE put on some pants".
It is not. The base value stored by minimumProfit has values added to or subtracted from it when the player uses "companion share" to put things in or take them out of the companion's inventory, but the base value itself is raised each time something is added to the companion's inventory by an AddItem command in a script or dialogue result field. For the average companion mod, that means the base value of minimumProfit bounces all over the place, and "minimumProfit == 0" says nothing about how much the NPC is wearing or carrying.
Special local variables that were not present in plain Morrowind, and would therefore have no effect even if defined in a NPC's script, may be made to work by importing the related GMST from Tribunal into the companion mod, even though this GMST only holds the string value for the variable, and doesn't contain any code. I understand that this is how a Morrowind-only companion mod can make companion work, allowing the player access to the companion's inventory, although I haven't gotten this to work yet.
| Back to modding quirks |
Apart from the Get/Set/Mod commands for skills and attributes, there are also Get/Set/Mod commands for certain effects, like Attack and Chameleon. If I use "player->SetChameleon 100", either in a script or from the console, the player should become undetectable. I used it in a script to give the player total undetectability for 60 seconds. It didn't work. I thought Sneak was also needed, added a variable to store the player's Sneak skill, used "player->SetSneak 100" and saw that, again, it didn't work.
(At least, it seemed not to work. The player doesn't become transparent but does apparently become invisible to NPCs. My player character was still detected by NPCs, however.)
So I created an Ability - a spell that, when you add it to the player, instantly affects the player, instead of going into the player's list of spells - which sets Chameleon to 100 and fortifies Sneak by 100. No need to store the old Sneak value now, the game will take care of that. And the script added the spell, counted to 60 and then removed the spell. This time, I saw the player become see-through as a sign that it worked.
See Making an actor lie down for another instance where adding an ability to the player did the trick, and Witchwither and paralysis for another actor attribute that doesn't work as expected when changed by Set/Mod commands.
| Back to modding quirks |
My test case is Mistress Dratha. This venerable old magic user has maybe two spells in the game. That's not much, so in a general startup script I added more spells to her character. Then, I added some dialogue so that she wouldn't just get the ring of Black Jinx delivered to her; she would have to work for it, by battling three atronachs in the Arena. And they killed her every time, because she just wouldn't use the spells that were added.
So in the script that made her fight the atronachs on entering the Arena, I also added scrolls to her inventory that she might find useful. She only had two scroll types in her original inventory, one for spell absorption and one for a shock attack, to which the storm atronach was, of course, immune. No wonder he kept finishing her off. Checking her corpse, I found she had used the extra spell absorption and shock attack scrolls I gave her, but did she use the sixth barrier scroll? Noooo! I've noticed with companions also that they will not use healing scrolls on themselves in a fight, although they will use healing potions, which is a shame as NPCs tend to drink all healing potions at once but will only use one scroll at a time. At any rate, I conclude that as with spells, NPCs only use scrolls that they were given in the game editor.
So I added potions to protect against frost and shock attacks (being Dunmer, she was already fire-resistant, and the flame atronach was always the first opponent to go down) and lo and behold, she used them. So game AI will let NPCs use "strange" potions, at least during combat and defensively, and I assume if the potions are standard and not a player-brewed concoction (but this hasn't been tested).
With weapons and magical items, the opposite appears to be true, ie. attacking items are used instead of defending ones: NPCs will use a shock or frost ring in a fight, but not a healing ring. They do use a barrier ring, but not, it seems, a robe that casts Reflect. Sadly, guaranteeing an effect by letting them wear a "constant effect" item (a belt of Feather so they can carry more?) is a bad idea as "constant effect" starts to work backwards on an NPC after a while. Having an item that constantly regenerates health, but letting them wear it only during battles, might be a safe way of keeping them alive.
In addition, I gave Dratha three soul gems and made a script to let her cast Soul Trap on the atronachs (NPCs can cast any spell when the script tells them to, whether they have that spell in their inventory or not). This worked, that is to say, when she killed the first atronach, I received a message "you have captured a soul". I didn't check whether the soul had gone into a gem of mine or into the soul gems I had added to her inventory, but it was a moot point as, apart from the undesired message, she could only cast Soul Trap once before combat commenced and she skipped the next two times in favour of defending herself (I probably should have put a StopCombat in the script). So I just let her give me fully charged soulgems out of nowhere with AddSoulGem if she survived the fight.
| Back to modding quirks |
So I had a big messy unfinished mod that needs Tribunal and Bloodmoon, and I wanted to take out some stuff that didn't need Tribunal or Bloodmoon and put that in a Morrowind-only mod. But the big mod used a startup script to dress some nude people the first time it was run, and to check if any pilgrims needed to be removed from the shrine after a finished pilgrimage, every time it was run. And Morrowind uses only one startup script, which I'd rather not alter. A recommended solution for a script that only needs to be run once is to attach it to a ring or other small object, and then hide the ring in Seyda Neen, so it will be loaded, and its script run, when the player first steps out of the Customs and Census office. But my script needed to be run preferably at least once per game. So I attached it to four rings and hid these in Seyda Neen and three other places where the player was bound to go every so often (Vivec, Ghostgate, Balmora near Caius Cosades' house). They worked like this: if a CellChanged occurred (the player either wanders into or out of the cell where the ring is) then the script would run, and relocate NPCs if needed.
And it didn't work. When I started a new game, Morrowind responded to the script as it often does to scripts when Tribunal/Bloodmoon are not installed:
Trying to RunFunction index greater than function countAnd the script aborts. This is apparently because the ring's script doesn't wait for the intro and tutorial to finish, but runs straight away; a problem I've never had with the startup scripts that the expansions allow me to add. So in this Morrowind-only workaround-startup script, I have to add code to stop it running until the game has properly started:
if ( CharGenState != -1 )
return
endif
That took care of the error message, although the script still didn't run due to
a copy/paste error that produced a LeftEval error. That the CharGenState-related
error message has something to do with the difference between Morrowind and
Morrowind plus expansions is proved by a "Skip Tutorial" mod that I always use
which works perfectly under GOTY, but whose altered "CharGenDoorExitCaptain"
produces the same RunFunction message under Morrowind.
Having found that "startup" scripts need to be stopped from running too soon, I found another Morrowind-only quirk: CellChanged doesn't always work. In the Arena in Vivec, there is a hidden ring with a script attached, just like my "startup script" rings. And, just like those rings, the script only runs when "CellChanged == 1", in this case, when the PC steps into the Arena. Then, if there is any fight scheduled, the appropriate opponent(s) will be teleported into the ring. But I wanted to bring Dratha over to fight some atronachs. So I made a second ring based on the first, that also ran after a CellChanged event (but only if the first ring hadn't started any duels). In the GOTY plugin, I tested this and it works. In Morrowind, it did not. Apparently, if two scripts in one cell check for CellChanged, only the first one gets accurate feedback. So the second one has to use another method. Fortunately the second ring was only needed for one fight, so I could define a variable "doOnce" and use that instead of CellChanged.
Conclusion: the expansion sets not only add new features, but fix stuff that is broken: stuff that Bethesda's official Morrowind-only game patch doesn't fix.
| Back to modding quirks |
There is no single reliable way to detect if the player has left one cell and entered another in Morrowind; the go-to command is CellChanged, which is set to 1 for the first frame that the PC is in a new cell, but as discovered in Script quirks in a Morrowind-only mod, CellChanged isn't reliable even in the two cases it's supposed to be detected: when doorporting (passing through a loaddoor, which deposits the player in another cell), or crossing a cell border outside. Not only do competing scripts seem to snatch the CellChanged event away from each other, but if the loaddoor has a script attached, especially one that could cause a ForceGreeting, the CellChanged event may not even happen. An example is the chargen door (the final door out of Seyda Neen's office), which never raises a CellChanged when used, even though the script on it only leads to a ForceGreeting if the PC is newly created and tries to leave without handing over the identity papers. (This was the case for the first time I installed Morrowind from the GOTY Edition; I've since installed it from the Elder Scrolls Anthology, where CellChanged does happen once the script no longer causes a ForceGreeting, so this may have been patched; or maybe the Morrowind Code Patch fixed it.)
The many things that can lead to a cell change undetected by CellChanged are: travelport by boats, silt triders, guild guides and going to jail; magical teleport through Almsivi Intervention, Divine Intervention or Recall; any scripted teleports, whether through activation, dialogue or otherwise, like Propylon travel; and positioning commands in the console. At least travelports take followers along; the other forms of teleport leave them behind even if they cast the appropriate spell, because spells like Almsivi Intervention don't transport anyone but the player. (The Bloodmoon expansion adds a function to detect travelling, see Detecting travelling (Bloodmoon).)
In both OpenMW and the Morrowind Code Patch, this has been largely fixed: ForceGreeting no longer prevents CellChanged detection, and CellChanged should trigger in all cases mentioned above.
Without these fixes, cell change detection must use a combination of methods. Apart from checking if CellChanged was fired just in case, one infallible check is using GetInterior to see if the player has gone from the outdoor world to an indoor cell, or vice versa. GetInterior is useless for crossing into the next outdoor cell or passing a loaddoor from one interior cell to another; that is what CellChanged is supposed to be for.
An almost foolproof form of cell change detection, that requires long and complicated scripts, is comparing present and previous cell names using GetPCCell and a system to refer to cellnames by numbers, since the game engine will not permit the direct comparison of strings. It is perfect for detecting the change from one interior cell to another, as all interior cells have unique names.
Exterior cells, especially the Region ones, will happily share the same name, because, as opposed to interior cells, they have absolute coordinates, so cell names don't matter. There is still a way to pinpoint an individual cell: each exterior cell is a share of 8192 by 8192 units, the grid of cells spreading outward from the central point at coordinates 0,0,0. So every cell has two numbers; how far away it is from the centre on the x axis, and on the y axis. These numbers won't appear in the cell name, but they can be used with the console command "coe" ("center on exterior"); using "coe 0,0" will put the player at coordinates 4096,4096. That makes sense, as 4096 is half of 8192. When outside, divide the player's or other actor's x or y coordinate to find these two numbers; if they are different from the previous calculation, the actor has changed from one outdoor cell to another.
Other means of detection are less reliable. If an actor's coordinates change drastically from one frame to another, that could mean a cell change. But cells are big, and moving between interior cells may not affect coordinates at all, eg. when doorporting from coordinates 0,0,0 in a fort to coordinates 0,0,0 in that fort's dungeon. If a player has a follower and GetDistance between player and follower is 0, it is very likely that player and follower just doorported or travelported, but this lasts barely more than a frame as the follower quickly moves away from its target, so a script would have to use GetDistance every frame, which burdens the CPU. What a script can do every frame is detect the magic effect of Almsivi Intervention, Divine Intervention or Recall (they last one frame, except Recall which lasts two frames), but though these guarantee that a teleport happened, they don't guarantee a cell change; that temple or Mark may be a short distance away.
A totally unreliable attempt to detect the transition from fake-outdoor interiors (like the open spaces of Mournhold) to proper interiors, is via GetWindSpeed, which is 0 in normal interior cells, and 0.01 or more in fake-outdoors interiors - but only if the wind blows, which it doesn't do constantly.
| Back to modding quirks |
There are many ways for the PC to cellhop without triggering CellChanged. One of these is travelport, ie. being teleported from one exterior location to another, through dialogue, ostensibly by boat or silt strider. The Bloodmoon expansion has kindly added a function, GetPCTraveling, which returns 1 while waiting for the journey to finish and the destination cell to load. The game uses this to prevent inopportune werewolf transformation. I simply want to know if the PC has just travelported.
So I want to set a variable if GetPCTraveling returns a positive. I could test for "GetPCTraveling == 1" every frame, but I only want to set the variable once, at the start of the journey. So I put it in a script that has one of those "if menu open then return" code blocks. And it's never set. Not only is the black "travelling and loading new cell" screen seen as an open menu, but the travelport starts from dialogue, that is to say, from an open menu. The minute the PC arrives, GetPCTraveling returns 0 again. So, this condition has to be tested inside the "if menu open" block.
I want to process the variable after the menu closes, which is too late to set it. So I want to set it, once, when the menu is open, and wait for the menu to close. If the menu is closed, but the variable is set to 1, that means that travelling has happened. I can then run whatever post-journey code I want, and reset the variable for the next trip.
; make script loop until menu is closed
if ( MenuMode == 1 )
if ( hasTravelled == 0 )
if ( GetPCTraveling == 1 ) ; only when menumode is 1
set hasTravelled to 1 ; set once
endif
endif
return ; do nothing util menu has closed
else ; menu is closed
if ( hasTravelled == 1 )
[whatever code depends on player having travelled]
set hasTravelled to 0 ; reset
endif
endif
The same construction has to be used when testing for GetPCSleep, which is also 1 while a menu (in this case, the resting menu) is open, and GetPCInJail, a second function added in Bloodmoon for the same purpose as GetPCTraveling. Sleeping doesn't change the player's location, but going to jail does.
GetPCTraveling does not return 1 when being teleported from one Magic Guild hall to another by guild guides, perhaps because the teleports, which are always from interior to interior, are instantaneous, whereas time passes during normal travelporting (and sitting out a jail sentence).
GetPCCell is said to take part of a name, as in "Fort Frostmoth" which will return true for all cells whose names begin with "Fort Frostmoth,". Using "Old Mournhold" it did not return true for all of Old Mournhold's cells, which begin with "Old Mournhold:" - note the colon instead of the comma. I tested if the cell "Vivec, Jeanne: Trader" would return true for
GetPCCell "Vivec, Jeanne"and it didn't. Neither did
GetPCCell "Old Mournhold:"with colon included, for any cells in Old Mournhold. My initial conclusion was: if there is a colon in the cellname, GetPCCell can't recognize it unless it's written out in full.
I have since figured it out, from wondering why the Tribunal expansion contains three unused interior cells, one of them even completely empty: the string used to refer to all cellnames starting with it, also has to exist as a cell itself. Those three unused dummy cells allowed GetPCCell to cover almost all of Tribunal's added cells: due to the dummy "Mournhold" cell,
GetPCCell "Mournhold"did not only return true for the Mournhold town cells (Palace, Bazaar) but also for "Mournhold Temple: Reception Area" and all other Temple rooms with colons in their names. Due to the dummy "Mournhold Temple", I could also use
GetPCCell "Mournhold Temple"to check if the player was present only in the Temple part of Mournhold. The first two dummy cells contained unfurnished buildings, but the third, "Sotha Sil,", was empty, just a defined interior cell and nothing more. Creating another such a cell with the name "Old Mournhold", I could suddenly use
GetPCCell "Old Mournhold"(colon not needed) for any Old Mournhold room, instead of having to write all the room names out in full and testing every one of them separately.
Finally, if GetPCCell (or coc in the console, or PositionCell to an interior cell) doesn't work even though the cellname looks correct, it may be that the cellname contains extra spaces. In the script editor, with its proportional font, two spaces often look like one. Solution: type out the cellname in an editor using a font of fixed width, like Monospace or Courier New (the "typewriter" font), copy, and paste.
| Back to modding quirks |
When querying in the console (and probably in a script) how far away a NPC is from the player, "[NPC-id]->GetPos X/Y/Z" only works if the NPC is in memory, which is true when the player has recently talked to, or entered the same cell as, the NPC, or if the NPC is in a cell near enough to be loaded; how many adjacent cells are loaded when the player enters a cell is determined by the game settings. As long as the NPC is loaded, GetPos will work no matter how far removed the player is from the NPC, although they should be in the same "world" - either outside or in the same interior cell - else the coordinates are useless to calculate distance. GetDistance, which requires no calculation from the scripter but weighs on the CPU, always works.
Likewise, SetPos only works when the NPC is in memory, while GetCurrentAIPackage always works. (SetHealth won't work and and GetHealth shouldn't, although GetHealth has worked from the console.) I tried to use GetPos, GetCurrentAIPackage and GetHealth to see if a companion 1. is loaded in memory and 2. is still following me, but have decided that to check the first, I should use a targeted script containing GetHealth without prefix, since that will return 0 when the companion is no longer loaded, while GetPos seems to return nothing at all.
When PC and companion are in totally different worlds, ie. at least one of
them is in an interior cell, GetDistance will always return:
340282346638528860000000000000000000000.00
which, I've found, can't be stored in a variable, maybe because an ordinary
float can't store a value that big. So whenever I compare the outcome of
GetDistance to a value to see if a companion is in a different (interior) cell,
I have to write this value out in full.
Both "[NPC-id]->GetDistance player" and "player->GetDistance [NPC-id]" only work on the FIRST INSTANCE of the NPC, and can't be used to check how far away the PC is from multi-instance NPCs like guards. For such NPCs, it is possible to use "GetDistance player", without prefix, from a targeted script, but then the problem is how to target the script at the right instance; the only way I know is to start the script from a result field, which means the PC has to talk to the NPC, in which case the NPC is close enough to be in the PC's line of sight. A second possibility is to use "GetDistance player" in the local script of the NPCs, which isn't a problem when adding new NPCs in a mod, but which in the case of already existing NPCs, like guards, means adding or altering a standard script, and hoping another mod won't undo the changes.
| Back to modding quirks |
The global variable GameHour wraps. All by itself. This occurred in Morrowind with all expansions installed, and wasn't tested further, but this is what I discovered through a script to advance the game time. If it's 23:00 and I add 2, the game time automatically becomes 01:00 - no need to calculate the value.
Global variables pertaining to date - Day, Month, Year - also run from 1 to a certain number and then go back to 1, although I haven't tested whether they wrap around automatically when their value is raised in a script. According to MSFD9, Month wraps around wrong in the game itself: it goes from its highest number to 1, even though the first month's number is 0, so basically a month is lost every year. This has been fixed in the Unofficial Morrowind Patch, an ESM file that corrects many game bugs, by running MonthFixScript.
Because the script I was working on calculated the date 8 hours into the future, which meant that I also needed to know the new day if the 24-hour mark was passed - and, due to wraparound, today's Day might be 30 and tomorrow's Day might be 1 - I used DaysPassed instead. This global variable, introduced in Tribunal, keeps track of the days passed since the start of the game, so tomorrow's value will always be higher than today's. It should also work in Bloodmoon, but hasn't been declared there, so anyone who uses DaysPassed with BloodMoon alone has to define this global variable in the mod. Whether DaysPassed would be updated by the game if declared in a Morrowind-only mod, I don't know. (In a test in an older installation, it did not. However, see DaysPassed turns up in Morrowind-only installation.)
| Back to modding quirks |
Beggar's Nose is a spell that may be added to the player through a birthsign; it exists only in the birthsign "the Tower", and has a placement count of zero in the editor.
This is a problem when using GetSpell. Say, a NPC offers to teach the PC spells, but not, of course, the spells that the PC already knows. And the PC has been given Beggar's Nose by choosing the right birthsign. The following:
player->GetSpell, "beggar's nose spell"will return 0. After using
player->AddSpell, "beggar's nose spell"GetSpell will still return 0. If the player didn't get that spell through a birthsign, GetSpell and AddSpell will work as expected.
How did I know this? Because I had a dialogue option to make a NPC teach the PC a number of spells in a specific order, Beggar's Nose being something like the fifth, and I noticed that for player characters with a birthsign that already conferred this spell, there was no way to learn the spells after it. I couldn't test for the spell, and I didn't know how to query the birthsign - besides, other mods might add new birthsigns with that spell - so I just scrapped the spell from the list.
| Back to modding quirks |
There are two commands, GetParalysis and SetParalysis. One would think that this means paralysis is an on/off setting: 1 means paralyzed, 0 means not. But no. I discovered this when adding code to a companion script to make the companion auto-cure himself of diseases, poison and paralysis, if he had the right spells and number of magicka points, or items like scrolls and potions. Mostly, the code worked. The problem lay with paralysis, and particularly Witchwither, a common disease that causes paralysis.
Paralysis usually happens when fighting scribs and/or magic users. So as to be able to test the self-curing code without getting into a fight, I opened the console and used "mycompanion->SetParalysis 1". He didn't cast an unparalyzing spell because he was paralyzed, but he did "drink a potion" against paralysis, which means a script had something cast the potion's spell on him, and it didn't work; GetParalysis still returned 1. Apparently SetParalysis works differently from paralysis caused by spells.
So, in the console, I used "AddSpell witchwither", since a disease, like an ability, is a kind of constantly self-casting spell, and casting a regular spell on him myself would have made him hostile, and was not guaranteed to succeed. This led to a catch-22, as he had both a disease and paralysis. He couldn't cure the disease first because he was paralyzed, and if he had loads of potions of Cure Paralysis (and not Paralyzation, Bethesda), he would drink them all, because the still active common disease would reparalyze him as soon as he deparalyzed himself. The only solution in this case was to use SetParalysis to set his paralysis to zero, then have him cast a spell or drink a potion against disease.
Since paralysis, as I've discovered, is not an on/off setting, it is unwise to have GetParalysis test whether paralysis is 1 or 0. Firstly, paralysis can run from 1 to... well, I don't know what the maximum is, but at least 5. A potion or spell of Cure Common Disease will get rid of disease-induced paralysis completely, but NPCs can't reliably use potions, nor can they cast spells when stiffened. Especially with Witchwither which keeps incrementing paralysis, it's a bad idea to code something like "if GetParalysis=1 then SetParalysis 0". Similarly, paralysis can be below zero, ie. negative, so a companion unparalyzed by using up a stack of potions or having paralysis decremented by 1 several times over, can't be tested with "if GetParalysis=0". I had to check whether paralysis was above 0, or less than/equal to 0.
| Back to modding quirks |
I wanted to make the player train his/her own companion. This is pretty unbalancing, so I added a malus: it would be very, very tiring. The first time, they would even drop from exhaustion together to drive home the point. How to get them horizontal?
Playgroups have been differently implemented between versions of the game executable, but let's just assume everyone out there has a completely updated game. I used playgroup Death1 and Death5 (lying face up and face down). This only changes the legs; if the arms were in a fighting or shooting position, they stay that way. If the NPC was running, they even continue to move. So the arms must already be made "idle" (not engaged in a different animation) before those playgroups are used. And the playgroup command will not work on the player.
So, let's use Fall! Set Fatigue to 0, tell NPC to Fall. First, all ongoing animations must be stopped by setting the playgroup to Idle, or the NPC will simply keep going and flop over unexpectedly later. Secondly, the NPC will only fall when trying to move and finding there is no energy to even keep upright. The moving happens when the NPC is in following mode. I thought I had to switch off following mode, but the problem turned out to be that Fatigue was rising and with it, the NPC's ability to stand upright. (Of course I used ModCurrentFatigue, not SetFatigue which changes the base value.) So I had to make a script that kept the NPC's Fatigue below zero by constantly lowering it. The NPC does have to stay in following mode to fall down at all. Also, the falling shouldn't happen until the talking dialog is closed, so the script should check if all menus are closed before continuing. Now, I have a NPC lying at my feet. The script that keeps lowering his Fatigue has a timer and will stop running after, say, 60 seconds. (What position you get from Fall seems to be random; when falling to the side, I noted the arms do change, otherwise, they may stay in the last position they were in before falling.)
An observation here is that ModCurrentFatigue used on the NPC does not automatically minimize itself to 0; a command "ModCurrentFatigue -100" on a NPC with 40 Fatigue sets the Fatigue to -60. I don't know if this applies to all characters or only to those with "auto-calculate" unticked. A Fatigue of 0 or less forces the NPC to fall but still allows the NPC to follow when the PC gets too far away.
Since follow mode is what keeps my companion down, the companion will rise to follow me and then drop again if I walk away. The companion will also rotate if I walk around him. I've since found a better alternative to AIFollow: use AIWander with a wandering distance value (the first number after "AIWander") of, say, 512. The companion, freed from following mode, will immediately walk away and, therefore, flop over, and remain down as long as the Fatigue-draining spell or script is running. Apparently, issuing the AIWander command also resets animations, because I didn't have to bother with setting playgroups to Idle, and no longer needed Fall.
There was another problem: the companion, though now immobile, would still address and greet me, and if I activated him by pressing the Space bar, even with companion share disabled, any nearby NPC would hiss at me for supposedly trying to steal! The solution was to not only set the companion's greeting distance to zero with "SetHello 0", but to also deflect activation by adding "OnActivate" to his local script, plus code that would let me activate him only if he was conscious; to know when that was, I let the companion-dropping script set a global variable to one value when he fell down, and another when the timer ran out and he got up again. (Instead of a global, I could have used a local variable or a light, but I wanted the value to be set even if I walked away, and a NPC's local variables stop updating once the PC is too far away.)
Here is an example of using OnActivate with a global in a companion script:
if ( GetFatigue >= 0 ) ; knocked out
if ( CompDown == 0 ) ; Don't set global every frame, only going down
set CompDown to 1
endif
else
if ( CompDown == 1 ) ; Reset once no longer knocked out
set CompDown to 0
endif
endif
if ( OnActivate == 1 )
if ( CompDown == 1 ) ; should be on the ground
return
else
Activate
endif
endif
Note: if GetFatigue is 0 but the NPC hasn't finished its animation for falling to the ground, Activate still fires, and the player is still accused of pickpocketing, so in practice I use something like a counter to delay setting the variable when Fatigue first dips below 0.
A warning about Fall: its purpose is to impart the effect of smacking on the ground after a drop from a certain height. It will have no effect if the NPC just keels over, but a NPC who is scripted to teleport to a lower location with PositionCell and then Fall, may fall all the way down and die as a result. Since Fall changes a teleport downwards into a fall downwards, the NPC may also get caught on any obstacles in the fall's trajectory. More reason to just use AIWander (or AIFollow towards some stationary actor) with Fatigue below zero.
So that's how to make an NPC lie down for any length of time. Making the player lie down doesn't require Fall or AIWander, but has its complications. I can use ModCurrentFatigue to lower the player's Fatigue below 0, but this will only work for a moment. I tried to use SetFatigue 0 on the player and ended up with a player stuck to the ground with a worm's eye view of the surroundings until drinking a potion from its inventory, after which it had full Fatigue again - recalculated from the stats? - but before that, the player couldn't move, and for some reason I couldn't even switch to third person mode to check its position. Had the player's inventory been empty, I would have been stuck.
The solution was a spell, in the form of an Ability that always succeeds, which drains an enormous amount of Fatigue. Using the old method - a timer that drains fatigue every frame to keep it below zero - not only made it hard to fully restore Fatigue after the timer is done, because it may be deeply in the negative by that time, which somehow interferes with calculation (maybe the value was too large to fit in a variable of the short type?), but also, specifically for the player, blocked third person view through both tabbing and scripted Force3rdPersonView. Simply adding the ability with AddSpell, and removing it after the timer runs out, made the player fall down, stay down and get up again without side effects.
Although they still need AIWander, SetHello and something to prevent activation when lying down, this fatigue-draining Ability is also the best way to knock out NPCs.
| Back to howto |
A NPC in following mode is with you all the way. Kill someone? The NPC will help you without question. Grab stuff that isn't yours? The NPC stays mum and will even carry your loot, companion share permitting. But put the NPC in Wander mode by telling them "stay here", and their conscience returns. Ooh, thief, murderer! Worse, their outraged cries attract others, and if you're unlucky, those others will be guards. Ditto the NPC who is intended to be your companion: do anything wrong before you've asked the NPC to follow you, and you may find yourself running from, fighting or killing your intended companion. The following is info that I got from MSFD9, put to practical use:
I can add the standard local variable "nothief" to the NPC's script and set it. What this does is block the uttered accusations, not the mounting rage itself. This rage is programmed as the NPC's Alarm value: the higher the Alarm, the more likely the NPC is to attack. The commands dealing with the Alarm value are GetAlarm and SetAlarm (default value: 30). The variable OnPCHitMe is set to 1 not only if the player actually hits the NPC, but also if the NPC sees the player doing something naughty. So, in the NPC's personal script, I add code that sets Alarm to 0 whenever ONPCHitMe is triggered.
For this particular companion the script only sets Alarm to 0 before the companion and the player are properly introduced, as the companion is supposed to criticize the player later. It may be useful in the script of a companion not meant to block the player. Usually, companions will follow the player so closely that the player ends up getting jammed in doorways. Some, like Princess Stomper's "Breton Hunk Companion Lite", will always obligingly step out of the way, and others have an option to let me choose between these two modes of behaviour. What happens when the companion gives the player some space is that this companion is set to Wander, which turns on a personal collision detection, until the distance has widened enough, and then switched back to Follow. In that interval, the companion may suddenly subject the player to a barrage of accusations and even become aggressive, unless subdued using nothief and SetAlarm based on OnPCHitMe.
| Back to howto |
This is the same for player and NPCs: if the actor's Z coordinate (in Morrowind, Z is the height coordinate) is below the waterlevel, the actor is in water, whether wading, swimming or submerged. What the waterlevel is, depends: outside, it's always 0; in an interior cell, it can be set to anything, and despite a water level being set, there might not even be water in the cell. From Tribunal upwards, GetWaterLevel returns the current water level in interior cells (because, in Tribunal, this water level can change) but there is no function that returns whether water is present.
So, outdoors, the test is simple:
short inWater ; 0=no, 1=yes
short zCoord
set zCoord to ( [actorID]->GetPos Z )
if ( zCoord < 0 ) ; Actor broke water surface
set inWater to 1
else
set inWater to 0
endif
If, outdoors, in a body of water, the actor's Z coordinate is 0, the actor is not in water, but ON water, probably waterwalking. I've noticed that when waterwalking outside, the player may dip below 0 sometimes, so if waterwalking is active, the player could still be "in water" from time to time. The player will certainly be "in water" when waterwalking into something like the wholly submerged lower deck of a shipwreck, so I can't assume that a state of waterwalking (detected with GetWaterWalking) automatically raises an actor above the water level.
Interiors are another matter. From Tribunal onwards, you can use GetWaterLevel and compare zCoord to that, but since the waterlevel can change every frame, the two must be compared every frame, even if the actor is not moving. And if the actor's Z coordinate is below the water level, that's no guarantee the actor is touching water.
To detect the presence of water, sadly, there's no escaping a sound test using GetSoundPlaying. (And whether that test will work when sound effects are turned off, I have no idea.) GetSoundPlaying will only detect sounds from a specific source, so you need to either use the actor's local script, target the script on the actor, or use the actor's ID as a prefix in a global script. That means you can't test for ambient sounds like rushing waterfalls. The only sounds allowed are made by the actor: walking in water, swimming in water, landing in water, being submerged and drowning. I start with the first, so that water can be detected the first moment the actor dips its toes in; because in vanilla Morrowind, the sound type can be used to guess the water level.
short localVar ; set to 1 at the FIRST sound detected
if ( [actorID]->GetSoundPlaying, "FootWaterRight" == 1 ) ; wading (when moving)
set localVar to 1
elseif ( [actorID]->GetSoundPlaying, "FootWaterLeft" == 1 )
set localVar to 1
elseif ( [actorID]->GetSoundPlaying, "Swim Right" == 1 )
set localVar to 1
elseif ( [actorID]->GetSoundPlaying, "Swim Left" == 1 )
set localVar to 1
elseif ( [actorID]->GetSoundPlaying, "DefaultLandWater" == 1 ) ; dropped in water
set localVar to 1
elseif ( [actorID]->GetSoundPlaying, "drown" == 1 ) ; submerged
set localVar to 1
elseif ( [actorID]->GetSoundPlaying, "drowning damage" == 1 ) ; drowning, losing HP
set localVar to 1
else
set localVar to 0
endif
At least the sound test only needs to be carried out when the actor is below water level, and a counter can be used so that if the actor walks around below this level for a number of frames without water sounds, it is assumed the cell is dry. The sound test does have to be repeated every time the actor enters a new interior cell (which is harder to test for than it seems, see Detecting cell change), and while indoors, the water level has to be queried every frame. The in-water variable now also has a value that means "there is no way of knowing yet". However, once the water-in-cell test is conclusive and the water level is known, testing if the actor is in water is the same both indoors and outdoors. This second test is in a separate block below the water test block, so it can profit directly from the water test result, instead of having to wait for the script's next iteration.
short inWater ; 0=no, 1=yes, -1=unknown
short zCoord
short TestWaterInCell ; 0=no, 1=yes, -1=unknown
float CellWaterLevel
short WaterCounter
[code: if actor entered new cell]
if ( GetInterior == 1 ) ; inside
set TestWaterInCell to -1 ; needs sound test!
set inWater to -1 ; how can we know?
set WaterCounter to 0 ; reset counter
else ; outdoors, no water test needed
set TestWaterInCell to 1
set CellWaterLevel to 0
endif
endif
; These values must be set every frame.
set zCoord to ( [actorID]->GetPos Z )
if ( GetInterior ) ; inside
set CellWaterLevel to ( GetWaterLevel )
endif
; If unknown, test every frame or until WaterCounter is too high
if ( TestWaterInCell == -1 )
if ( WaterCounter > 120 ) ; 120 tests turned up nothing??
set TestWaterInCell to 0 ; We're done looking for water!
; Only test if the actor is under the known water level!
elseif ( zCoord < CellWaterLevel )
set WaterCounter to ( WaterCounter + 1 )
[insert sound test, setting inWater to 1 if a sound is detected]
if ( inWater == 1 ) ; found it!
set TestWaterInCell to 1
endif
endif
endif
; If water present, same test for all cells:
if ( TestWaterInCell == 1 )
if ( zCoord < CellWaterLevel ) ; Actor broke water surface
set inWater to 1
else
set inWater to 0
endif
elseif ( TestWaterInCell == 0 )
set inWater to 0
endif
That covers it for Tribunal, although the water counter could do with a refinement: an actor can stand still in water and make no sound, and if the water counter runs out before the actor has moved, the water-containing cell will be assumed dry. And 120 frames is, on average, only 2 seconds! So I would only raise the counter if the actor has moved, which I can check by comparing the actor's X and Y coordinates in this frame to those in the previous frame. However! A teleport, which also changes the X and Y coordinates, can plunge the Actor knee deep in water without making a sound, so the water remains undetected. It's unlikely that the actor will teleport into water 120 consecutive times, but still, using the water counter to limit the sound test makes this method less, ahem, watertight.
Vanilla Morrowind is harder: indoor water levels always stay the same, but there is no direct way to query them. They can only be guessed through the sound test, which means that the sound test determines whether there should be a sound test! An unworkable situation.
The thing is, I have no way to find the waterlevel without hitting water, so I'll never be able to confirm that an indoor cell is dry. At most, I can confirm that the water level is lower than the actor's Z coordinate, and push the assumed water level further down as the actor descends; any time the actor's vertical position is higher than this assumed water level, I don't need to test. Each time the actor descends, I push the level down after 120 frames of not encountering water; the water counter gets recycled within the same cell. If I do hit water, the water level question is settled, and I shouldn't need the sound test any more.
The first block stays the same. The second block now only only fills zCoord. In the third block, if TestWaterInCell is -1, CellWaterLevel is set to zCoord, the actor's vertical position, plus 100 to force a test; then TestWaterinCell is set to 0. The sound test is run as long as TestWaterInCell is something other than 1. If the water counter runs out before water sounds are detected, the cell is not set to "dry", but the presumed water level goes down by 10, and the water counter is reset for another test run. (Again, I would add code to make sure the frame counter only goes up if the actor moves, but I want to keep the example simple.)
; If inknown, set a possible water level to find out
if ( TestWaterInCell == -1 )
set CellWaterLevel to ( zCoord + 100 )
set TestWaterInCell to 0 ; but test anyway
endif
if ( TestWaterInCell != 1 )
if ( WaterCounter > 120 ) ; 120 tests turned up nothing??
set CellWaterLevel to ( CellWaterLevel - 10 ) ; Let's try lower
set WaterCounter to 0
; Only test if the actor is under the known water level!
elseif ( zCoord < CellWaterLevel )
set WaterCounter to ( WaterCounter + 1 )
The sound test is a bit different, setting inWater, but also a presumed water level, being the actor's Z coordinate plus 10 if wading, 100 if swimming, and 200 if submerged. This is because the average height of a humanoid actor - the kind that makes these water sounds - is just under 150 units, so if the actor is in deep enough to swim, that would put the water level at least 100 units above the actor's Z coordinate. An actor falling in water is also typically halfway under when the splash sound happens, and drowning means that the water level is above the actor's height. Since not all humanoids are the same size, this is a very inaccurate way of measuring the water level, but fear not; this will be somewhat corrected.
; Begin soundtest, setting inwater to the value to raise CellWaterLevel by
if ( [actorID]->GetSoundPlaying, "FootWaterRight" == 1 )
set inWater to 10
elseif ( [actorID]->GetSoundPlaying, "FootWaterLeft" == 1 )
set inWater to 10
elseif ( [actorID]->GetSoundPlaying, "Swim Right" == 1 )
set inWater to 100
elseif ( [actorID]->GetSoundPlaying, "Swim Left" == 1 )
set inWater to 100
elseif ( [actorID]->GetSoundPlaying, "DefaultLandWater" == 1 )
set inWater to 100
elseif ( [actorID]->GetSoundPlaying, "drown" == 1 ) ; submerged
set inWater to 200
elseif ( [actorID]->GetSoundPlaying, "drowning damage" == 1 )
set inWater to 200
else
set inWater to 0
endif
; End soundtest
The most accurate measure of the water level is when the actor dips its toes in, especially when that actor is waterwalking. But sometimes water is discovered through a loaddoor that is underwater on both sides, or by falling in, or the whole cell is full of water. Hence, when TestWaterInCell goes from 0 to something else, the estimated water level may be far too high. Now that the cell is known to have water, TestWaterInCell is set not to 1 but to 2, so the sound test keeps happening, but CellWaterLevel is adjusted each time the actor enters the water while wading. (At least, that is presumed; teleports straight into water can still muck things up.) Since waders tend to be up to their ankles to make a sound, the water level is always set to zCoord and 10, except if the wader is water-walking, upon which - hallelujah! - the water level is presumed known and TestWaterInCell is set to 1.
if ( inWater > 0 ) ; we struck gold, I mean, water!
; The first high estimate
if ( TestWaterInCell == 0 )
set TestWaterInCell to 2
set CellWaterLevel to ( zCoord + inWater )
set WaterCounter to 0
; Refine the estimate for waders
elseif ( inWater == 10 )
if ( [actorID->]GetWaterWalking == 1 ) ; YES!!
set TestWaterInCell to 1 ; DOUBLE YES!!!
set CellWaterLevel to zCoord ; you know it's right!!
else
set CellWaterLevel to ( zCoord + inWater )
endif
endif
set inWater to 1 ; the other test won't work until TestWaterInCell=1
endif
endif
endif
The final test, checking if the actor is in water by comparing the Z coordinate to the water level, stays the same, although it relies on TestWaterInCell being set to 1, which won't happen in most interiors - and even if it does, when you leave and re-enter them, the whole process starts over! - so it will mostly be used outdoors. ohshit, rewrite. tempval to see if differs from last inWater, if so, for first 4 sounds, add 10, else 100 bookmark HIERWASIK met howto. review next and finetune one after that.
I thought I could test the degree of immersion (wading, swimming, submerged) from the sounds detected, but for "Swim Left/Right" and "FootWaterLeft/Right" this doesn't work, possibly because they all refer to the same soundbyte. "DefaultLandWater" (the splash of falling in) is a different soundbyte, as are "drown" and "drowning damage". To cover all possible situations of immersion, these sounds should be tested for, but if the player goes from "dry land" to "submerged" from one frame to another, then clearly "set WaterLevelInCell to ( GetPos Z )" will not accurately detect the water level. Ditto the "fall in water" sound, where the actor may already be some distance below the actual water level when the sound is detected.
| Back to howto |
The short answer is: you can't. Neither in Morrowind alone, nor with Tribunal. Apparently NPCs could be made to use a potion with Equip in Morrowind, where the function is broken; I haven't seen this myself. In Tribunal/Bloodmoon, where Equip does work, the PC can be made to "equip" (drink/eat) potions and foodstuffs/ingredients, but not put on wearables, while NPCs can be made to equip wearables (clothes and armour) but not potions - they are "equipped", ie. separated from the stack like a worn item of clothing, but not drunk - or scrolls, or ingredients/edibles, to which the same applies; and all "equipped" scrolls, potions and ingredients are unequipped as soon as any of them are lifted from the inventory via companion share. The only item type both PC and NPC can be made to equip is weapons.
So, how about AIActivate? Apparently this can make NPCs open loaddoors and pick up weapons. This function was likewise broken and said only to work with potions before Tribunal, although it doesn't seem to work with potions at all. I tested some situations and the results were inconsistent, probably because I didn't know what I was doing: did the item have to be in the NPC's inventory, or (as with loaddoors) outside and close to the NPC? AIActivate was said to make NPCs pick up a weapon, which suggested the latter. In any case, AIActivate will not work with a potion name (if the code was in the result field, an error check in TESCS produces a message that a creature/NPC of that name can't be found; the same message also appears when typing the code in the console) and won't do anything while the dialogue window is open. In the console, while leading Menelras (a buyable slave) around, I used PlaceAtMe to create a Scroll of Vigor and "menelras->AIActivate sc_vigor" to make him pick it up and read it: this caused a loop in which new scrolls were being opened for taking or closing (by the player!) all the time until the game crashed - because scrolls (like soulgems, see What NPCs will and won't use) are always aimed at the player? Maybe that's why NPCs won't use scrolls that were not in their inventory when they were created? When a potion is placed near the NPC and the NPC is told "NPC->AIActivate potion" in a script, nothing happens.
On a potion in a companion's inventory, AIActivate seemed to work if there
was only one of the chosen item in the inventory. If it wasn't in the inventory
at all, then AIActivate didn't work unless I used AddItem to add something
different as well. To quote my (tidied-up) notes:
Case: NPC has no potions in inventory, "NPC->AIActivate potionname".
The first time this is used, nothing happens. After a different potion is
added and used with AIActivate, it works. This seems to be reset by changing the
NPC's health from the console.
Case: NPC has no potions in inventory, "NPC->AddItem potionname 1", "NPC->
AIActivate potionname".
It works and the potion disappears from the inventory.
Case: NPC has no potions in inventory, "NPC->AddItem potionname 1", "NPC->
Equip potionname".
It works and the potion disappears from the inventory. (Equip should not
need AddItem, but else the encumbrance might be messed up.)
But then it didn't work any more, or maybe it never really had worked, so I
gave up and tried a different approach.
I took up the suggestion of MSFD9: make a spell that does the same as the potion (which is a spell cast on the drinker) and use that spell instead, also removing one potion from the inventory to give the impression of having used it. There were several options.
1. An ability, plus timer code. The ability, a constantly self-casting spell just like a disease, is added, then removed when the timer runs out. Advantage: the drinker stands still and doesn't do the spellcasting animation. Disadvantage: keeping track of the timers, which requires scripts and makes it difficult to apply to more than one NPC. Also, an ability can't stack, ie. I can't lower a NPC's intelligence 20 points per jug of sujamma. Actually drinking the potion does make the effects stack.
2. A spell that works on self, cast by the drinker on the drinker. Advantage: a spell has its own timer. Disadvantages: the effects don't seem to stack, casting it again when "drinking" a second potion just prolongs its duration; the drinker goes through a spellcasting animation; and since Cast needs an explicit target even for spells cast on self and the only reliably present target is "player", it seems that the drinker is attacking the player. Paralyzing the drinker while the spell takes effect does not work, as the spellcasting animation is simply delayed until the paralysis is lifted.
3. A touch spell cast on the drinker by someone else. Advantages: the spell has its own timer and the drinker doesn't move. Disadvantages: the effects don't stack and the caster can't cast on just anyone, the target has to be put after the command, so this is only useful for one NPC, like a companion, and not for just any old NPC you meet. The caster can be anywhere, so you can have a spell cast by the distant, hidden Lord Cluttermonkey, but must be alive (getting Ralen Hlaalo to cast spells didn't work) or the spell can be cast by an object or static, but in that case the object/static must be nearby or still loaded in memory, else having it cast a spell through a script will abort the script, even if its "persistent" box is ticked, and in any case the casting will fail.
4. A spell that works on self, cast using ExplodeSpell. Advantage: the spell has its own timer, and no target is needed, since ExplodeSpell is supposed to be cast on itself (by objects rather than actors), so it can be used on any NPC. Major disadvantage: ExplodeSpell doesn't exist in Morrowind, it was added in Tribunal. Other disadvantages: the drinker still casts a spell, but now turns around and away from the player to do so, and the effects still don't stack.
What absolutely doesn't work: giving the NPC a weapon with a "cast once" enchantment. Say, a sword that, each time it's wielded, makes the NPC's health go up by 20. That would stack every time the sword was re-wielded, right? Just make the NPC wield the sword with StartCombat, quickly followed by StopCombat to nip the accompanying verbal abuse in the bud. Nope. Apparently such enchantments can't be cast, least of all on self, except through a scroll, which NPCs can't be scripted to use. Weapons only have "cast when used", so the NPC has to clock someone, or "permanent effect" which is no use when simulating potion-quaffing. Or there's the option where the actor casts a spell added to the spell list by wielding/wearing an item (like Fargoth's healing ring), which actors also can't be scripted to do.
However: there is light at the end of the tunnel. A new version of the Morrowind Code Patch, which already stopped NPCs from guzzling all their healing potions at once, will also allow them to self-prescribe magicka potions and - here it comes - drink potions when scripted to Equip them. Meanwhile, since NPCs automatically drink not only health but also fatigue potions, it is possible to create a potion that has the desired effect plus fatigue increase, put it in the NPC's inventory, suddenly lower the NPC's fatigue with a script, and hope this will spur the NPC to drink the potion.
| Back to howto |
Why would I want stuff to go poof when I drop it? Maybe I have something of which only one reference should ever exist, and I don't want extra copies lying around. Maybe I'm using a copy of an in-game object and want to change its icon art to something unique, but don't want to have to change the 3D mesh that appears on the ground when the object is dropped. In any case, this is stuff that should not exist outside my inventory.
There is a special variable, OnPCDrop, that will set itself to 1 if you declare it in a script and attach the script to the object that has to disappear when dropped. This script will then make the object disappear by disabling it and, if Tribunal or higher are installed, marking it for deletion. This happens so fast, you'll never see the object hit the ground.
The thing is, you can't simply disable it at the moment of dropping it. Nor can you mark it for deletion at the moment you disable it. These have to be done in consecutive frames, so every action is tested for and followed by a return, with the last action that needs to be taken, put first in the script. Since this explanation may not be terribly clear, here is the code:
short OnPCDrop
short WaitForDisable
; Comment out this code in vanilla Morrowind
; This happens 1 frame after the object is disabled, 2 frames after it's dropped
if ( GetDisabled == 1 )
SetDelete 1 ; command added in Tribunal
return
endif
; End comment out code
; This is every frame after disabling, unless you can delete the object.
; If you can't delete the object, at least cut the script short with Return.
if WaitForDisable == 2
return
endif
; This happens the frame AFTER the object is dropped
if ( WaitForDisable == 1 )
set WaitForDisable to 2 ; for when you can't use GetDisabled
Disable ; it's no longer in inventory!
return
endif
; This is the last thing in the script, but the first thing that happens.
if ( OnPCDrop == 1 )
PlaySound "potion fail" ; whooshy sound effect
set WaitForDisable to 1
set OnPCDrop to 0 ; good coding practice but not needed in this case
return
endif
| Back to howto |