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 the Morrowind 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 swimmming 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.
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 and discovered a few things, not all of them mentioned in this document. So, for anyone surfing on the quirks of Morrowind modding, I thought I'd share.
Undocumented keys
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 while skipping 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. 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.
Crash caused by battery
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.
TESCS, Morrowind and GOTY
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.
Syntax, and lazy syntax
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:
Script says it doesn't exist
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. Because
of the incomplete way scripts are compiled, they should, for safety's sake,
always be saved twice.
I've found a second and even more amusing error. I create a new spell and, in
the same editing session, add "AddSpell spell-name" to an existing script.
Before saving (ie. compiling) the script, I change the spell's name in the spell
editor (open spell, change ID, choose No to question "create new object"). Then,
I try to save the script. It claims the spell doesn't exist! Because I hadn't
compiled the script for the old spellname, I don't know whether the step of
changing the spell ID was even necessary; apparently if you open a script window
and then start adding spells and other objects, the script compiler won't
know about the new objects added since the script window was opened. The only
solution was saving the whole data file (with the script window still open, so I
don't lose the script changes) and then reloading the whole file. Then I opened
and saved the script again, and it compiled.
Missing persons and scripts
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 them 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 PositionCell used in the result
field is said to cause crashes).
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. It
might be that a script can refer to instances that are placed in-game
with the editor, like the many guard clones.
Missing objects and scripts
If an object ID referred to in a script doesn't exist or hasn't been placed
in the game (or was made after the script was opened for editing, see Script says it doesn't exist) then of course the
script won't compile. If the object is referred to in the little script known as
the 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 count as "placed in the
game" since they are only in an inventory, therefore, I can't use GetDistance on
them in a script.
"Error check results" changes global variables
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 global variables; 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 this global variable. The solution is to i.
always check 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.
MessageBox
The results 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:
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 script of the NPC/creature addressed. In a result field,
it is not possible to declare a local variable, and no local variables from
other scripts can be displayed. In a script, I tried to display local variables
from another script in the following way:
Standard display variables
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. Now,
"dialogue", ie. displayed text, can also be put in the result field, using
"MessageBox" and the text string; the text will be displayed in a different
colour and the same variables can be used, but with a caret instead of a percent
sign: ^Name, ^Class, ^Race, ^PCName, ^PCRace. I haven't tested them all, but for
^PCRace, this does not work. 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 said in Morrowind Scripting for Dummies, ^Name will just display
the value of ^PCName, so in result fields the name of the addressed NPC should
be used, while in a script a paraphrase is needed ("your companion", "your
slave"). ^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:
Dialogue strings: allow yes to all
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:
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.
Doors and their instances
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.
Health and death
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 with
"npcname->SetHealth 0" 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 nothing will change; in that situation a
script using GetHealth will act as if the NPC's health is 0, so it should use
GetDeadCount for a more accurate reaction to the situation.
It is also not possible to remotely kill NPCs from a script with
"npcname->SetHealth 0". If they are in the same cell or already loaded, their
health will at least be set to 0. If they are in the same cell as you, 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 for his little death-grunt before disabling him 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.
Odd things when resting and levitating
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.
However, you can also lose companions. When teleporting by opening a door to
another cell, or by making use of travel services, if the PC is levitating, any
following NPC is 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.
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.
PositionCell: observations
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, 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.)
PositionCell will work in cells that haven't been visited yet, and for NPCs
that haven't been loaded yet - as opposed to SetPos, which only works if the NPC
is loaded and in an active cell (preferably the cell the PC is in, I don't know
whether there is a difference between interior and exterior cells). There is a
potential problem with this. If a NPC is teleported 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. And if this 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. 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
above, because I haven't had crashes as long as I don't teleport "new" NPCs into
the PC'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 SetHealth -20.
Unfortunately, neither AddSpell nor SetHealth 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 health.
Using a nonexistent cell name with PositionCell lands the teleported
character in the first existing cell, somewhere in the Ashlands: at coordinates
0, 0, 0 in cell 0, 0 to be precise.
Two quirks discovered while working on a companion: if, in an active cell, a
script teleports an NPC towards, for instance, a shopkeeper, and adds shopping
items to his inventory to the point of overburdening him, he snaps back to his
old position. Teleporting and adding items in two different scripts would
probably prevent that. If, again in an active cell, a script makes him cast a
spell and then teleport, he casts the spell, but PositionCell doesn't happen, as
if the spellcasting cancelled it. I tried making him cast a spell in the result
field and then use a script to teleport him; this caused a CTD. Finally I put
the spellcasting back in the script and made him wait 4 seconds (too short and
the spellcasting cancels the teleporting, too long and he starts greeting the
PC) before teleporting with PositionCell.
The fourth numerical argument for PositionCell is the Z angle, the direction
in
which the NPC faces. This is said not to work, but it does. 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.
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 Position and
PositionCell don't 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 standing next to me
somewhere on the Bitter Coast to Balmora caused a CTD.
Armour 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 use local variables in the result field, so here I would
need to use global variables. I opted to run a script 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 PC. They will not speak out when the PC commits a crime, and
when they attack something, the PC bears the blame. (And gets the experience
points?) But companions also somehow inherit the PC'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 (N)PC, 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 the reference, I deduce that these
commands simply don't work for armour (and possibly weapons). I can only
"damage" armour by placing it in the game with the editor, and then editing its
reference data.
Straight from the result field
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 not 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
topics that depend on items in the inventory, but conclude that any action which
should update topics belongs in the result field, not in a script.
The PlaceAtPC method
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 Morrowind only than in the GOTY installation.
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, but when
I left the cell and then revisited it later, they did disappear, but with an
error message.
The lights method
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 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 PC was active. I hit on the idea of putting the script in a new
kind of light and adding this light to the PC's inventory, where it would do its
counting invisibly and then remove itself from the PC's 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, deleting
an item with a script from one's inventory even through an external script will
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 PC'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 PC's inventory, as there is no safe way to delete
them.
Adding lights to a non-standard 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 even though holding nothing visible, and light will reflect
off the slave. So I stored the slave's status in a global variable. In short:
lights are fine for transferring the value of local script variables (which
can't always be queried directly) in short interactions, but should preferably
be cleared from the NPC's inventory when no longer needed.
Lying down
I wanted to make the PC 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 the NPC down, the NPC will rise to follow me
and then drop again if I walk away. The NPC will also rotate if I walk around
him. So I have to disable PC movement until the timer runs out. But if I talk
to the NPC, even though he is following me, even with companion share disabled,
any other NPC will hiss at me for supposedly trying to steal! I don't know if
this is due to the game itself or one of the many little tweaking mods I
downloaded, but the safest method is to disable everything with
DisablePlayerControls and only reenable everything after the NPC has had his
little nap - leaving me stuck for the full sixty seconds!
So that's how to make an NPC lie down for any length of time. I still don't
know how to make the PC lie down. I can use ModCurrentFatigue to lower the PC'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
PC couldn't move, for some reason I couldn't even switch to vanity mode to check
its position. If the inventory had been empty, I don't know what I would have
done.
Conclusion: I've found out how to floor a NPC, but have yet to come up with
a solution for the PC.
Evil twins: duplication of a unique NPC
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
After that, teleporting the remaining NPC instance had the effect of
disabling it. I tossed the game and started a new one.
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 to tell one mod to not load the NPC if another mod has already loaded
it. The only solution: make an ESM which does nothing but place the NPC in the
cell. Then have both plugins depend on the ESM.
Journal error in result field
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, though the NPC's script contained no errors. Apparently 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.
Topics, the blue stuff
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.
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 list of topics, 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 (ie. active) 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.
Deleting and importing topics
On a modding forum, I found the method to remove unwanted parts of a plugin:
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. However, each dialogue string is
linked to a string before and a string after it, and deleting added dialogue 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 set the topmost
string to Ignore. 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.
Journal entries are usually ordered from 0 to whatever their highest number
is. But sometimes, the order is reversed. I can now guess why. When I export an
added topic 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 ordered backwards. This is not a problem with
journals, but can be with topics 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 by setting any string
that isn't added or genuinely changed to Ignore. This happens with every import,
so, importing dialogue is a pain.
Local, global, targeted, variables
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
close enough to the PC (main game character); it can't be stopped with
"StopScript". A global script is not attached to any actor or object, so there
is no default target, as it were, to apply commands to; also, it runs non-stop
no matter where the PC is, although it can be stopped with "StopScript". A
target script is a kind of ad rem local script: it is not attached to anything,
but can be told to attach itself to an actor with "Actor->StartScript
TargetedScript" and then acts like a local script, only it also keeps running no
matter where the PC is, and can also be stopped with "StopScript". This info is
straight out of MSFD9, but here's why I'm repeating it:
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 own script. This script does not have direct access to the
companion script variables, so I code it:
This doesn't work. And if I put this line in a result field, I would get a
complaint that SetHealth (and by extension any Set/Mod command) can only take
local variables. I can't define variables in a result field, but this is
a script, so I add variable "tempval" to StayWithCrassius:
This compiles without problems, but still doesn't work. Oh wait, the script
is not targeted (it's called once and terminates itself after running) so maybe
I ought to specify whose health should be set?
This will produce an expression error of the LeftEval type when the script is
run in the game. The only way to get around that is to make the script targeted.
So in a result field I put:
At this point I should mention that there are also three kinds of variables:
global, local to the script you're dealing with, and local in other scripts.
Actually, there are just two types of variables, global and local, and I only
divided the local type into "local" and "remote local" because of problems like
the case above, where one script can't access the local variables of another.
Global variables are accessible by all scripts, and always keep their value, but
can update a bit slowly (eg. I can set the global variable that changes a NPC's
responses from normal to happy, but it takes a while for those happy responses
to appear). Local variables in local scripts assigned to objects/actors also
keep their values, and can be accessed through "ThisActor.localvar", although,
as shown above, this doesn't always work. Local variables in global scripts lose
their value after a while, and are accessed through "ThisScript.localvar" which
likewise doesn't always work. Local variables in targeted scripts are accessed
like those in global scripts, since the targeted script is a form of global
script, and I don't know if they retain their value; probably not. In any case,
all these types and subtypes of variable don't like being mixed. To avoid
strange LeftEval and RightEval error messages, it's best to define a "short
tempval" in every script that interfaces with other scripts and/or global
variables, and transfer any outside variable's value to this tempval for further
processing.
I should also repeat something else from MSFD9: if a script contains a local
variable defined in another script, but not in itself, the compiler
doesn't report this error, and the script malfunctions in the game. Because I
use tempval and helpval in various scripts, I don't get a warning if I use
tempval in a new script that has no "short tempval" at the top. This had me
tearing my hair out and cussing the TESCS until the penny dropped.
Trying to make an expression work too hard also causes eval errors, and I'm
not always sure what causes them, too many arguments or the arguments being
mixed local/global variables. Here, the cause has to be too many arguments:
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 (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 only solution is to remove
arrows in a loop, one at a time, checking with GetItemCount in each iteration
whether all the arrows are gone yet.
What is funny about accessing remote local variables is that, without
targeting, you can set their values but not get their values.
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 (where the
NPC has its own script and local variables) will work:
Block if-endif too large
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 smaller blocks, for instance, five if-elseif-elseif-etc
conditions, then, if one of these has been satisfied, a StopScript/return,
then the next five if-elseif-elseif-etc conditions, and so on.
Dialogue and greyed-out fields
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 and changed the
NPC ID from "blank" to 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 select the right
NPC ID again. In this case my mistake was to leave a value in the Class field,
but no doubt the same applies to the other speaker fields.
Special local variables
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 something like
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 depends on a script and not on the
NPC. Not that it matters, as a targeted script is still a global script and only
one instance of a global script may be run 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 also discovered, in an effort to
add choices to slave dialogue through a script that displayed the same choice
options after every response, that local variables from a local script also
can't be queried by a targeted script. As an example, say I add 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. So I
don't have 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 speaker. 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:
SetChameleon
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 PC
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.
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 PC become
see-through as a sign that it worked.
GetDeadCount and the prefix
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:
The Firemoth plugin
Not quite about modding, but falling under "technical information": 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.
Collision boxes and the quicksand effect
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.
Expansions and missing dialogue
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. 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
showed up. For "specific place", the original and expansion dialogue were not so
neatly separated. That's why, although I thought I'd added lines in the "old
dialogue" section, they were in fact under an "expansion dialogue" line. The
plugin relied on "Morrowind.esm" only, but the line ID that my added lines
started 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 Morrowind-only line. I
moved the lines so they came under Elone's reply about places the player should
know about, and that solved the problem.
Two ways to cause a CTD through dialogue
The first way to crash the game through code in the dialogue result box is
one that I could have seen coming. Morrowind Scripting for Dummies
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 alter-inventory code 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.
What NPCs will and won't use
The case for this tip 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 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 a potion 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 it. 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: they
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. Letting them wear an item
that constantly regenerates health only during battles might be a safe way
of keeping them alive.
In addition, I gave Dratha three soul gems and 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.
So I just let her give me fully charged soulgems out of nowhere with AddSoulGem
if she survived the fight.
Shushing a companion
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 Morrowind Scripting for Dummies, 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 above scripting guide tells
me that the variable OnPCHitMe is set to 1 not only if the PC actually hits the
NPC, but also if the NPC sees the PC 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 PC so closely
that the PC 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
OnPCHitMe.
Game lag in Windows 7
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 clicking the MSE executable, which will then
start Morrowind. If I clicked the MSE executable, nothing happened; if I then
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 the standard Morrowind: 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.
Script quirks in a Morrowind-only mod
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:
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 the Morrowind-only game patch doesn't fix.
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".
MessageBox "all the text you like"
or:
MessageBox "text text text %g text %g" variable1
variable2
where %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 line
or:
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 results field, and
you run "Error check results", a MessageBox line that is too long 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".
MessageBox "INB level %g willp %g" "npcname".inblevel
"npcname".inbwillpower
but presumably because of the quotes, the variables were seen as buttons. So
in the script with the MessageBox, I declared a variable "helpval1" and
"helpval2", filled these with the values of "npcname", and used them in the
MessageBox line.
"npcname"->PositionCell 0 0 0 0 "^Cell"
Too bad, eh.
AllowYesToAll=1
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.)
SetHealth mycompanion.basehealth
short tempval
set tempval to mycompanion.basehealth
SetHealth tempval
short tempval
set tempval to mycompanion.basehealth
mycompanion->SetHealth tempval
mycompanion->StartScript StayWithCrassius
instead of just
StartScript StayWithCrassius
and now the script will work. The "mycompanion->" bit in
"mycompanion->SetHealth tempval" is now optional, but the tempval variable is
not; Set/Mod commands don't like directly being assigned other people's
variables.
set tempval to ( tempval + ThisActor.localvar1 +
ThisActor.localvar2 )
The error was an EXPRESSION error, maybe from using the same operator twice?
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, this was 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 )
set companion.INBintelligence to ( companion->GetIntelligence )
set companion.INBathletics to companion.realathletics
But this line will not:
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.
if ( stayOutside == 1 )
instead of
if ( menelras.stayOutside == 1 )
since it should work for every slave. It won't. When the script
encounters this remote local variable, it stops running. I had to put the
choices directly in the result fields instead.
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.
Trying to RunFunction index greater than function count
And 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 this error
message has something to do with the difference between Morrowind and Morrowind
plus expansions is shown 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.