Morrowind Scripting for Dummies
Morrowind Scripting for Dummies, 9th edition, is the most comprehensive Morrowind scripting guide available.
It is hosted at Morrowind Modding History (archive link) and Morrowind Nexus.
Although this book is the most complete mwscript reference available, it still contains errors and omissions (some inherited from the original Construction Set manual). The foreword already alludes to this by noting that different versions of the game might have contained different bugs. In addition to any Bethesda fixes, the MCP has also fixed a great number of potential crashes, potentially rendering some of the advice in the guide out of date.
When looking up a function in Morrowind Scripting for Dummies, do read beyond the description and through its notes and comments, which contain valuable information. Also check the Known Errors and Additional Notes below (these were mostly compiled by Rotouns).
Known Errors
Activate
There is no undocumented feature by specifying the player after the function (->Activate, player
). The addition of , player
is simply ignored, and it does the same thing as Activate
alone. In the given example, "container"->Activate
will work, but only if the container has been manually opened in the session before, and loaded within 72 hours.
AddSpell
The note "creatures killed with curse spell effects on them cause all other creatures of that type to have the same curse on them": this has nothing to do with the creature being killed or the effect being of the Curse type. Just like NPCs, abilities, diseases or curses added to a creature ID by dialogue results or their local script will be added to any new instance of the creature.
AiActivate
Can not make a NPC drink a potion.
It will only pick up objects in the cell; does not check for objects in inventory. AiActivate
doesn't work if you give a reset
argument. As always, only the first object found will be targeted, so the object needs to be unique and have "references persist" ticked if the AiActivate
line is called in a compiled script
Note: The Morrowind Code Patch feature Scriptable potion use
lets you make a NPC drink a potion with the Equip
function instead. This always worked for the player, and what MCP does is extend it to work with NPCs too.
GetAIPackageDone
The following isn't true if a [reset]
argument is used: "When you leave a cell with an Actor that is just performing its AITravel
command, or if you rest, the script will never detect the GetAIPackageDone
signal". But keep in mind that when AiTravel
is skipped by resting, the engine doesn't check path collision, so the NPC may be teleported to a spot where it's stuck (like inside another NPC) and where it may not acknowledge that it has reached its destination, or only after a delay or when the player reenters the cell.
[reset]
The undocumented argument for Ai functions (AiTravel
, AiActivate
, AiFollow
, AiEscort
...):
- For
AiTravel
, it changes the behaviour of the "package done" flag.GetAiPackageDone
normally only returns 1 for one frame, when the Ai function has been executed. The[reset]
argument of Ai functions is optional. If it is given with any value (0 or 1), there are two effects forAiTravel
. First, once theAiTravel
package is executed,GetAiPackageDone
will NOT reset to 0 until a new Ai command is called on that NPC (if the new Ai command is called while inMenuMode
,GetAiPackageDone
will reset when out ofMenuMode
). The point of this is that scriptedAiTravel
sequences won't break if the single "package done" frame is skipped for any reason – in vanilla it would be for instance skipped if the PC waits/rests during the execution ofAiTravel
. The second effect forAiTravel
is that in cells without pathgrids, without the reset argument, when the actor collides with any static objectGetAiPackageDone
returns 1 once and the travel package stops; but with the argument,GetAiPackageDone
doesn't stop returning 1 and the actor will keep trying to reach its destination. - For
AiWander
: the[reset]
argument has no effect onGetAiPackageDone
. Wander packages restart at the end of their duration with or without the reset parameter, and when resting until after the end of anAiWander
cycle,GetAiPackageDone
doesn't stay 1. - For
AiActivate
: the function does not work if a[reset]
argument is given.
GetTarget
This command does not "check if the target is currently in the crosshair".
Its function for the player is exactly the same as for NPCs: checking if the target ID is engaged in combat by the calling actor.
For NPCs, GetTarget
returns 1 not only in combat with the target, but also (if the target is the player) while they speak a Hello
voice.
The duration during Hello is inconsistent in the original engine (1 second after reloading into a cell, otherwise the duration of the first voice ever spoken before, or 3.4 seconds when first loading an interior).
Get/Mod/SetPCCrimeLevel
"influences NPC disposition" is by default incorrect. The value of the GMST that would control this: fDispCrimeMod = 0
ModPCFacRep
Modifies or defines the reputation
(not reaction
) modifier for members of the specified faction towards the PC. This affects advancement for NPCs of the same faction, but doesn't directly affect their Disposition (only Rank affects the NPC's disposition if the NPC's faction is favorably disposed towards the player's faction).
Note: Faction reputation is completely separate from general reputation and is not involved in the dialogue function PC Reputation
; similarly, only general reputation, player's and NPC's, is involved in Persuasion success formulas.
OnPCHitMe
The simple declaration of the variable does not usually prevent combat reactions for NPCs with standard AI values for Fight
(30) and Alarm
(90). The report that "an NPC with a script that uses this variable will not attack on its own accord. If you don’t want the Actor to remain passive you have to manually StartCombat
" seems to be consistently true only for NPCs with less than 76 Alarm
, or after a script stops that NPC from attacking the player at least once with Stopcombat
and Setfight
. For NPCs that are supposed to be passive, Stopcombat
needs to be used under OnPCHitMe
. For NPCs that are not supposed to be passive, using StartCombat
manually is still recommended, as there are cases where OnPCHitMe
does prevent normal NPCs from fighting back, even with more than 30 Fight and more than 90 Alarm (seems to be related to crime having happened in an interior outside of the reaction range of these NPCs, and gets reset if PayFine
is used with a guard nearby).
OnPCSoulGemUse
Is nonfunctional. Since soul gems are hardcoded Misc items, the only way for it to work would have been to add a script with it to the default soul gems, but even then the variable is not detected to be set when a soul gem is used and deleted during enchanting. Azura's star script is also not what makes it work.
PC Clothing Modifier
"The value of your equipment changes the disposition of people in the game" is incorrect. While the base game files can randomly show favourable greeting text when equipment value is high, actual disposition is unmodified and those same NPCs can therefore still alternate between laudatory and hateful greetings.
PlayGroup
, LoopGroup
Unlike what the note states about "crosswired" animation, function names do not give different results if they are used in the console or in scripts. They seemed to give different results because of scripts compiled in old mods (the compiled script is saved in the plugin along with the text of the script by the CS; what the game processes is not the text of the script but the compiled version). The explanation is that these scripts were compiled in an older version of the CS, before a change in Bloodmoon: see this page for the affected Opcodes. The NPC animation explorer linked in MSFD is one of these older mods and needs to be recompiled, otherwise it will fail or play wrong animations.
Position
, PositionCell
Can NOT take variables, local or not. See additional notes on PositionCell
below for more quirks.
Rotate
, RotateWorld
While Rotate
doesn't have the same issue as RotateWorld
with not updating the object's coordinates for GetAngle
, it will make both position and angle changes NOT persistent if the game is saved and reloaded in the same cell. See Move
, MoveWorld
about this. See SetAngle
, GetAngle
about the order of rotations.
StartScript
Unlike what the warning states, it does not re-run other scripts, and scripts could not "be run more than once in the same frame".
SetJournalIndex
The condition for a value set by SetJournalIndex
to persist is not simply for the journal to be "defined in the "info" section of the dialogue window": the player must have received an actual journal entry for that journal ID using the function Journal
before (can be any valid entry in the same journal, greater or lesser index value does not matter). Otherwise, if no valid entry was given in that journal before SetJournalIndex
is used, the issue is not only that the value will be lost when a savegame is reloaded: if a save is reloaded to an earlier point (before using SetJournalIndex
) without exiting the game, the value set by SetJournalIndex
will still be there; if the game has been exited before reloading, then the value will revert to the last valid entry.
StopCombat
The fact that it "stops combat for all actors involved" is only true in the situation where StartCombat
was used to make one idle NPC attack another (StopCombat
will stop combat for both), or if NPCs were following each other and one was made to attack another/the player by script. StopCombat
will not make several NPCs stop attacking the player if they attacked separately. If NPC1 first had StartCombat
on player, and then also had StartCombat
on NPC2, once NPC2 reacts by attacking, StopCombat
on NPC1 will stop NPC1 from attacking the player but will not stop NPC2 from attacking NPC1 (who will then attack NPC2 back).
Targeted scripts
"Object_ID"->StartScript "Script_ID"
does work from dialogue results, not just scripts. Will not "only work to target the actor the player is in dialogue with"; provided an instance of the target exists, it is possible to specify a different ID in dialogue results.
Displaying variables and text defines in a message box
The digit designation for %g
is not ignored. Precision and significant digits for g
, G
are as described for the C language.
Referencing variables on other objects and scripts
- 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. - The cell limitation "
Set MyObject.variable to (...)
will only work if the cell containing the target object/(script) has previously loaded" is only correct for compiled scripts. With non-compiled scripts (dialogue results or console), the local variables of unique NPCs and creatures and reference persistent objects can be remotely set even if their cells were never loaded before, as long as an instance does exist in a cell. It's the functionsSetStat
,ModStat
... that don't work for actors that haven't been loaded in the past 72 hours. - If the object isn't a NPC or creature, it needs "
References persist
" to be referenced in another script, whether it is loaded or not. For compiled scripts, as always, one instance of the object must be placed in a cell in the CS and the first instance found is the only one affected.
Functions which are spelled differently than documented
getInvisible -> getInvisibile
is outdated ("fixed in a later patch or Tribunal?" according to UESP).
Additional notes
AddItem
, RemoveItem
- Usage with containers: containers become unique clones of their base object when they are opened, not when they are loaded in a cell. Adding or removing items before a container is ever opened affects the base object record, after it's been opened it only affects the instance.
- Usage with generic actors (creatures, or NPCs there are multiple instances of, such as guards): similarly to containers, adding or removing from an actor that was never loaded anywhere before will affect the actor's base object record, therefore applying the effect to every actor loaded thereafter. Adding or removing from an actor ID that has been loaded before will apply the effect to the earliest loaded instance, and to no other. Note: there is an apparent exception with actors placed by Morrowind.esm in the cell outside the starting ship, namely Seyda Neen, seem to get preloaded even if normal character generation was skipped. The unique actors among those then behave as expected of loaded actors even if clones of them are created, but while the three "Imperial Guard" instances placed in Seyda Neen also act as if loaded and are not targeted by later ingame base record changes, nonetheless remotely adding to or removing from "Imperial Guard" even while in the cell itself behaves as if none had ever been loaded and applies the effect to any later instance besides those in Seyda Neen.
- Adding a negative amount of an object or leveled list creates
maxcount
objects. - Without MCP, using
Additem
with leveled lists would otherwise work as expected on NPCs that were not loaded yet or on containers the contents of which were not changed by the player – otherwise, the leveled list would become visible in the inventory using the default missing icon and become interactable as a "NO NAME GIVEN!" object. - With MCP, usage with leveled lists otherwise works as expected.
AiActivate
[More info needed:] Repositioning an object and targeting it with AiActivate
for travel pathing may inconsistently lead to "Exception encountered saving AI Pack" errors?
In cells without pathgrids, unlike AiTravel
, AiActivate
doesn't get stuck when colliding with static objects, and is usually better able to reach the target. However, AiActivate
isn't usually able to cross exterior cell borders (activating the player seems to be an exception);
AiFollow
Outside of scripting, Follow
can also be used in NPC AI packages. If AiFollow
is under an AiWander
package (with infinite 0 duration) it only makes the actor join the target in combat, whereas if AiFollow
is on top it actually makes the actor follow its target when in range.
AiTravel
Outside of scripting, AiTravel
can also be used in NPC schedule AI packages – you can put several Travel
packages and have a basic patrol route included in the NPC's parameters. If you put Wander
packages with non-0 duration between the travel packages they'll wander at that point of the cycle then resume the travel patrol route (wander is in in-game hours at has to be at least 1 to not be infinite). Note however that in cells with no pathgrids they have a drawback compared to scripted AiTravel
: these NPC schedule AiTravel
packages behave like scripted AiTravel
with no [reset]
argument and will be interrupted at the first collision with a static object (the CS accordingly gives a warning when adding travel packages in a cell without pathgrids). AiTravel
with a [reset]
argument will keep trying to reach the destination.
AiTravel
and AiActivate
need a target within 7168 game units distance of the actor's current AI coordinates, otherwise they won't have the desired effect (they can still interrupt the current ai package or combat and make the actor return to their AI coordinates). These coordinates are reset when the actor is given new Ai targets, or when the actor follows the player (with AiFollow
- pursuing in combat doesn't count as changing Ai coordinates). If the actor has never been moved by Ai functions, these center coordinates are the actor's default initial position (if the actor was placed in the CS in the same cell with a wander package) or the actor's current position if it was created ingame.
AiWander
Note that the range is a spherical radius and doesn't mean the NPC won't wander outside of that area, but that the NPC will pick its destination within that range. The NPC can stray far outside of that range in order to reach a spot that IS within the range, like another floor.
"To let an Actor stand in one spot you can use: AIWander, 0, 0, 0
" – note that if no parameters are given for idle chances, they will be considered as 0, so while standing the actor will have no animations besides idle breathing. The most common idle chances in the game's content files are AiWander AIWander, 0, 0, 0, 0, 60, 20, 10, 10, 0, 0, 0, 0
.
Cast
, ExplodeSpell
Creatures with empty spellcast animations can not be made to cast spells by scripts, even though the AI can make them cast their own spells or enchantments. Examples of creatures with no cast animations: ash slaves, ash ghouls. Examples of creatures with cast animations: dremora.
ExplodeSpell
also works for "on self" spells, not only "on touch".
Scripted casting can be used by most objects, like activators and (persistent and unique) statics, but the casting object must have collision, otherwise the engine gives the error "Scale parameters for Magic Effect ... are bad". Note that the same error will be given if actors hit by spell effects are lacking collision, and that using SetScale
to make an object very small can result in it having no collision and giving the error.
Scripted casting should not be used by LIGHT
type objects, because it triggers a looping cast sound that cannot be stopped. The issue is probably related to the option those objects have for looping fire sounds.
Choice
Should never be used in Persuasion results: it will only direct to whatever topic was used last.
Disable
Disabled statics do keep their collision in interiors as well as exteriors and need to be reloaded/repositioned.
On the caution about disabling lights: changing the position of lights instead as suggested is a good workaround, but additional warnings on scripted lights: for lights that do not have a mesh, changes to their positions and script variables will persist through reloading games or starting a new game without quitting first. Adding a mesh (EditorMarker, invisible/collisionless) to the light seems to solve this issue.
Disable resets a NPC's voice line delays. A NPC that is disabled and enabled again (that can be done invisibly in the same frame) gets immediately prompted for a Hello
voice if the player is within distance of the NPC's Hello
parameter.
Drop
- Note that objects created by these functions are not affected by ingame lighting until the player exits and reloads the cell.
- Has been fixed by MCP: "When used on NPC's, the items are removed correctly from the NPC, but still dropped at the player's feet."
- Has been fixed by MCP: "If the player drops an item he doesn't have, his weight will still be reduced by the weight of that item - same as the encumbrance bug for
RemoveItem
."
ForceGreeting
ForceGreeting
allows you to bypass limitations for hostile NPCs (that you normally cannot talk to by activating them), but it still will not make you talk to a NPC that is knocked down or knocked out – "knocked down" comes from high weapon damage, "knocked out" starts with the first frame fatigue becomes negative.
Note about scripted animations: these states can only be triggered by damage or negative fatigue and not by scripted KnockOut
or KnockDown
animations - see Playgroup
, Loopgroup
; however, if the actor's fatigue was or becomes negative during a scripted animation, they will remain in a knocked out state during the animation even after their fatigue has become positive, until the scripted animation is finished and normal AI animation begins, or until changing cells or reloading the game. The same goes if the actor was in a knocked down animation before a scripted animation started.
ForceSneak
, ClearForceSneak
, GetForceSneak
Not usable on creature actors, even ones that use NPC animations or have the appropriate animations themselves.
ForceRun
, ClearForceRun
, GetForceRun
, ForceJump
, ClearForceJump
, GetForceJump
, ForceMoveJump
, ClearForceMoveJump
, GetForceMoveJump
Similarly not usable on creatures, even ones that use NPC animations or have the appropriate animations themselves.
GetButtonPressed
Note that the value GetButtonPressed
returns is consumed on use – the value HAS to be saved in a separate variable to be able to detect which of several choices was used – and only one script should check GetButtonPressed
at a time. If several scripts check it, only the first would get a value other than "-1".
Due to this, you shouldn't start an arbitrary MessageBox
with choices during MenuMode
(unless it is triggered by inventory or dialogue actions: those cannot happen during a different MessageBox
). As a rule, a MessageBox
with choices that is delayed or triggered by a timer should always wait until the player is out of MenuMode
.
GetCurrentAiPackage
Can never be 5 – Pursue
is never an active AI package.
GetDeadCount
- This is an extremely slow function if many different NPCs and creatures are recorded in the save. Make sure not to call it every frame.
GetDeadCount
will not increment if normal death animations do not play to the end (a scripted death animation does not count, see alsoPlayGroup
,LoopGroup
).GetDeadCount
increments at the end of the death animation, on the same frame asOnDeath
and can be checked at the same time (it will return an incremented value if checked underOnDeath
).
GetDistance
If the objects between which distance is checked are in different cells (different interiors, or one exterior and one interior), the value returned by GetDistance
will be the maximum possible value, 340282346638528860000000000000000000000.00. This gives you an easy way to check if a NPC was left in a different cell.
GetItemCount
GetItemCount
can detect leveled lists if they have not been resolved yet. Resolved refers to when the list's conditions and chances are applied, at which point it is replaced by items if any. For NPCs, leveled lists are resolved as soon as the NPC is loaded (when entering the cell they are in). For containers, leveled lists are resolved only when the container is opened.
OnDeath
, GetHealth
OnDeath
returns 1 only at the end of a normal, non-scripted death animation. MSFD suggests GetHealth
as an alternative to OnDeath
. However, a NPC can be dead with a positive amount of health if they were healing, because magic effects still apply during death animations. While there may (?) need to be at least one frame on which the NPC's health is < 1 for them to die, using GetHealth
is not a reliable way to tell that a NPC is dead afterwards.
GetPCCell
The function would return eval errors for cell names over 51 characters due to a bug with "if" conditions. This has been fixed by MCP.
GetPCSleep
Waiting or loitering (resting where sleep is forbidden) does not count as sleep. To detect it you can instead check if ingame time (GameHour
) changes within MenuMode
.
GetStat
, ModStat
, SetStat
GetStat
, SetStat
, ModStat
, GetSkill
, SetSkill
, ModSkill
... do not have any effect or return any value on actors that have not been loaded in the past 72 hours. The effects are also reset after 72 hours (see The "72-Hours Bug").
GetWeaponDrawn, GetSpellReadied
Both always returns 0 on a creature that doesn't use biped animations.
GetWeaponDrawn
seems to return 0 until weapons are actually attached in drawing animations. This explains the few frames' delay in the note which will also more generally apply when switching between weapons.
GetWeaponType
When a lockpick or probe was equipped, GetWeaponType
would return 0, the same as if a short blade was equipped. This could cause problems with weapon detection.
With the Morrowind Code Patch, GetWeaponType
returns -2 for lockpicks and -3 for probes.
GetCollidingActor
, GetCollidingPC
, HurtCollidingActor
Does not trigger when colliding with the top of an object: see GetStandingActor
, GetStandingPC
, HurtStandingActor
.
HasSoulGem
Does not work on containers, only actors. Returns not only 0 or 1, but the number of soul gems of all types that have the specified soul.
MessageBox
About giving messages with one or more choices during MenuMode
: a MessageBox
with choices is generally safe when it is triggered immediately by a player action (like doing something in the inventory or showing a dialogue result), because those could not happen with another choice message already on the screen. Otherwise, you should take care not to trigger the message with choices during MenuMode
, because there could already be a different one. See also the warning about GetButtonPressed
.
ModFactionReaction
Note that using this function will modify, then inscribe the current state of *all* faction properties of the first faction ID into the player's save. Includes faction and rank names, requirements, etc., which will overwrite the properties edits of any new mod that wasn't already installed when the function was used, and will definitively apply the properties edits of any mod that was already installed even if that mod is later removed. The same is true of ModRegion
and of NPC functions Mod/SetFight
, Mod/SetHello
, Mod/SetFlee
, Mod/SetAlarm
, AddSpell
, RemoveSpell
, SetHealth
which record all of the actor's base record properties into the save, even if there is no change or no existing spell to remove.
ModHello
, SetHello
Note that Hello
must typically be set to 0 during scripted NPC tasks, which can otherwise be interrupted for a dozen seconds by Hello
.
While the Hello
parameter cannot force a voice greeting, a NPC that is disabled and enabled again in the same frame gets immediately prompted for a Hello
voice if the player is within the parameter's distance.
Move
, MoveWorld
If a CS reference (an object instance placed in a cell in the CS, not created in the game) is moved with only Move
or MoveWorld
, its position will be reset if the game is reloaded in a different cell. Do not use timers to control Move
or MoveWorld
if position matters, because the player may save and reload while the object is moving, which could reset its position but not the timer. Assuming you use position checks, if you want the object's new position to be persistent, use SetPos
at the end of its movement (see comments on SetPos
for cases where SetPos
alone may not persist). If other functions like Rotate
are also used in the script, there are cases where SetPos
changes will not persist when saving and reloading in the same cell. When testing, keep in mind that reload persistence works differently if combinations of SetPos
, Move
, Rotate
... are used in the console, as opposed to used normally by local or global scripts: you cannot rely on console tests.
For GetDistance
not updating with Move
, see the note in MSFD on GetDistance
.
OnActivate
- "An item is usually activated when you press the spacebar to "use" it": it is also activated when another actor is made to activate it with
AiActivate
. - "so only one script can report
OnActivate
successfully, and you should only have oneOnActivate
call in your script at each moment": This also means it is bad practice to use it in a targeted global script, and it should be reserved for the object's local script. - "
LoadDoors -> Open
(If used withOnActivate
)": this madeCellChanged
not return 1 in the loaded cell, but has been fixed by MCP.
OnPCDrop
Detects objects dropped on the floor only, not in a container. Manual player dropping only: the Drop
function does not trigger OnPCDrop
.
OnPCHitMe
OnPCHitMe
cannot register the first 4 "friend hits" on a follower.
Note: there is a Friend Hit
dialogue filter for voices, although voices are not a reliable workaround for this and cannot be triggered during another voice.
PlaceAtPC
, PlaceAtMe
Note that objects created by these functions are not affected by ingame lighting until the player exits and reloads the cell. Does not apply to actors.
For creatures with particle effects: when a creature is created ingame by a script (as opposed to a creature or leveled list placed in the CS), constant particles that ignore parent animation and trail behind the parent (example: flame atronach body particles) are not displayed until the game is saved and loaded or, in interiors, until the player looks towards coordinates 0,0,0 where the particles are placed.
Static objects placed with these functions first have no collision until the cell is reloaded; having enable
called on them is one way to force their collision to work.
PlayGroup
, LoopGroup
, Animating objects p69
Playgroup
leaves the actor frozen after the animation if it does not loop: usePlayGroup idle
to reset the Ai controller and the actor will then resume normal behaviour.Playgroup
loops the animation by default if it has a loop. UsingLoopgroup
with a finite number of loops normally lets the actor resume its behaviour after the loops have played.- Neither
PlayGroup
norLoopGroup
will play the parts of animations betweenloop stop
andstop
- They play all animations except those of the root bone, which means the actor will animate in place and not move or rotate even if the animation is supposed to change their coordinates (like
WalkForward
). - Only animation groups without spaces in their name can be called by these functions (the weapon attack or weapon follow groups cannot).
- If
PlayGroup
orLoopGroup
interrupt death animations,OnDeath
will not return 1 andGetDeadCount
will not increase (unlessPlaygroup idle
is used to reset and let a full death animation play from beginning to end). If a NPC is scripted to die (SetHealth 0
) during a scriptedPlayGroup
orLoopGroup
animation,OnDeath
andGetDeadCount
will not update either. - Paralysis doesn't stop scripted animations, neither does the function
SkipAnim
. - On paralysis: however, a vanilla engine trick can use paralysis to freeze the scripted animation by ending it: first paralysing the actor like with
Setparalysis 1
, thenPlayGroup
with the desired anim, thenPlayGroup idle
during the scripted anim, which leaves it frozen in its current pose.
PlaySound
, PlaySound3DVP
The volume parameter must be between 0.0 and 1.0, the pitch parameter can also be over 1. Changing pitch does change the speed at which the sound is played.
PositionCell
, Position
- For actors: always use
AiWander
just afterPositionCell
, otherwise the NPC's AI coordinates will not change, and the NPC will reset to its previous AI coordinates if you rest in the same cell (other conditions to reproduce: rest must be in the same session, and the cell the NPC was in beforePositionCell
must be a cell that has been loaded in the same session before) - For actors: there are unwanted effects if
PositionCell
follows certain AI functions too closely in time. A delay between half a second and one second is the minimum you need afterStopCombat
; without that delay the NPC can decide to walk back towards its earlierStopCombat
position (if usingPositionCell
to move to another cell where that position isn't reachable, it can look like the NPC is jittering). NPCs may return to previously givenAiWander
positions afterPositionCell
if a new Ai target is not given. - For player: when positioning the player into a different cell, the condition chain that
PositionCell
is inside of can lose its state: the script resumes executing where it stopped, it does not restart from the beginning, but it forgets already checked conditions in the chain so any "else" or "elseif" won't work properly. You should either addReturn
at the end of the condition block that usesPositionCell
, or divide the condition chain after that block withEndif
. The bug always happens when teleported into a cell that was never loaded before, but it can also happen in already loaded cells if nothing in the cell was edited in a special way before (unlike with other bugs, callingEnable
orDisable
on objects does not count as edits for this, looting objects or killing actors do not count as edits either, but changing aFRMR
's local variable value does, if it is not changed back to 0). A similar issue exists withForceGreeting
. Example script for the issue:
short doonce if ( doonce == 1 ) return elseif ( doonce == 0 ) set doonce to 1 player->PositionCell 0 0 0 0 "Ghostgate, Tower of Dusk" else MessageBox "you should not see this message but you will" endif
- For non-actor objects: when
PositionCell
is used, if the object instance was already in the destination cell, the new position will not persist when exiting the program and reloading unless the instance has been marked as edited in the save, for instance by callingenable
on it or having one of its local variables set. Not an issue whenPositionCell
moves an object to a different cell. - The following should be fixed by the MCP: If
PositionCell
was used on a CS NPC instance that had never been loaded in the game before, in order to move it into either the current cell or a cell that had been loaded before, the NPC would appear without its local script and could cause crashes on activation.
Random
MCP fixes the bug that returned up to 1100 for specific values (65... 84) or crashed for multiples of 256.
RemoveItem
As long as an external script is used, removing a normal amount of scripted items from the player's inventory should not cause errors even if there are several. However, RemoveItem
can only remove one at a time. The report "if the player has two or more copies of an object with an attached script in their inventory, using RemoveItem
on that Object ID will frequently corrupt data for one of the remaining copies" is probably an unrelated error, or related to containers.
Resurrect
The values of local variables in actor scripts (but not their stats) are kept when they're resurrected. Resurrect
will also keep running any ongoing scripted animation (PlayGroup
, LoopGroup
) from the original actor, but will reset AI packages, like coordinates and wander, to their original default.
New instances created by Resurrect
also seem to preserve the full ingame ID of the actor (including the last 8 digits), which means that a resurrected actor can be targeted by compiled scripts that use its ID, just like the original instance (and unlike duplicate instances created with functions such as PlaceAtMe
).
SetAngle
, GetAngle
SetAngle X, Y
does not relate to X, Y angles in the Construction Set because the SetAngle
function does not apply rotations in the same order as anything else. The engine and the CS use the object's local axes and rotate in the ZYX order. SetAngle
uses local axes too but instead rotates in the XYZ order. By default GetAngle
returns the angle value set in the CS, but returns the value set in the game if SetAngle
is used, which is why you could use GetAngle
and get the CS value 10, then use SetAngle to 10 which rotates the object in a different position, then use GetAngle
again and still get the same value 10.
Note: The Morrowind Code Patch optional feature (not a fix enabled by default) Get/SetAngle enhancement
lets you use SetAngle
with U, V, W axes, which are mapped to X, Y, Z but applying a ZYX rotation axis order matching the rest of the engine. The patch also allows you to work with an actor's actual rotation instead of the actor's initial rotation.
SetDelete
- Objects marked for deletion are deleted immediately if they are new instances that were created while in the game (such as with
PlaceAtMe
), or deleted upon loading a saved game if they are instances defined in a plugin/master file. In the latter case, the player may already no longer collide with that object upon re-entering the cell. - If an object is effectively deleted while colliding with the player, the engine will crash.
SetPos
- For actors, the same warning as
PositionCell
applies: always useAiWander
just after usingSetPos
to change a NPC's coordinates, otherwise the NPC's AI coordinates will not change, and the NPC will reset to its previous AI coordinates if you rest in the same cell. - Corpses can be moved with
SetPos
if the corpse is afterwards (afterSetPos
) disabled and enabled. - For non-actor objects (similar warning to
PositionCell
for non-actors moved within the same cell): the new position will not properly persist when reloading a save if there isn't anything that marks the instance as edited in the save. Examples of marking an instance as edited: callingenable
ordisable
on it, or changing the value of any local variable in its script. Examples of moving an instance withSetPos
without marking it as edited: remotely callingSetPos
on a unique instance with "references persist" from another script, using SetPos in an instance's local script that uses a global variable and has no local variables... When testing, keep in mind that reload persistence works differently if combinations ofSetPos
,Move
,Rotate
... are used in the console, as opposed to used normally by local or global scripts: you cannot rely on console tests. For testing results, note that usingMove
orRotate
beforeSetPos
on the same frame may also result in the object'sSetPos
being saved.
SetScale
For objects with particles: in the vanilla engine, the effects of changing scale will apply twice for the particles (probably because the change applies both from the particles node and from the emitter), but for some objects (if the particles node does not have 128 in flags, example: light_torch_01
) the particles will have no scale change until the game is reloaded. For objects that are made of more than particles, large changes to scale will look bad since the effect will be squared for particles compared to the rest of the object (fire will be too big for the shaft of a scaled up torch, or too small if scaled down).
SkipAnim
Only skips frames of "natural" animations, does not interrupt scripted animations called by PlayGroup
or LoopGroup
. (See the earlier comments on PlayGroup
and LoopGroup
for a trick that does freeze scripted animations.)
ChangeWeather
TypeEnum
values 8 and 9 are added by Bloodmoon (snowy weather and blizzard).
Making Actors lie down
- 0 Fatigue: ModFatigue
and ModCurrentFatigue
will also fail to make a NPC fall down if they are triggered in the script by GetAiPackageDone
, meaning on the exact frame an Ai function (such as AiTravel
) ends. If a reset argument was given to the Ai function (see [reset]
above), the NPC will also get back up and start spinning.
- "Note that some character animation groups may not work as expected with Bloodmoon": no, this was a compilation issue with old mods, see notes on PlayGroup
.
- "Note also that the NPC's upper body may not play the correct animation": has been essentially fixed by MCP.
- "If you do use PlayGroup
, you will also need to stop NPCs using normal voices"... setting Hello
to 0 takes care of greeting voices and of another issue: NPCs turning around to face the player even while their animation is playing. Being hostile will still make them turn to face the player if they aren't paralysed.
Result field scripts
- Voice dialogue (
Attack
,Hit
...): if any input (except comments) is present in the result field and the voice is triggered while inMenuMode
, an error message is given: "Trying to RunFunction index greater than function count". Although harmless, the error will prompt the player to click "Yes" twice or it will close the game. If dialogue or the console is open and a NPC's health is set to 0, the error can be given by aHit
voice. In the course of normal play, this can happen forAttack
voices, since they can be triggered bySetFight
andStartCombat
in dialogue results (dialogue is inMenuMode
). NPCs who haveAttack
voices with scripted results can use their local script to detectMenuMode
and record it in a local variable as a condition for the voices to exclude. SinceGreeting
results apply on the first frame of dialogue (before a script can detectMenuMode
), that local variable should first be updated in the greeting's results if they includeStartCombat
. - Similarly, triggering
StartCombat
in the results of aHello
voice forces the inventory menu open and gives the "Trying to RunFunction index greater than function count" error, potentially leading to more AI breakage for the NPC/current cell. - The abovementioned errors amount to the engine trying to run a result script within a result script. The issue gets fixed by the current BETA VERSION of MCP 2.5b12, but is not in the stable version used by the public as of this writing [check if this is the case now].
Referencing variables on other objects and scripts
- As always, for this to work in a compiled script, an instance of the targeted unique object must exist (placed in the CS), and have "references persist" if it is not an actor. When used in dialogue results though (non-compiled script), any existing instance will work, even if it was created in-game and didn't exist in the content files. See also errors in the same section listed above at "Referencing variables on other objects and scripts".
- If for some reason a script refers to its own variables using the
"objectID".variable
syntax, for instance a local script used by several objects that wants to do something to only one of these objects, and if you have not compiled these variables yet (or accidentally saved this script by moving to another script in the window without compiling it), the "variable not found" error will prevent you from compiling the script, and it will not be able to see its own variables until it is compiled. You need to comment out the references to its own variables, compile, then uncomment and compile again. Something similar happens for self-terminating scripts, butStopScript
lets you compile the first time without commenting out the self-reference.
Script with style for safer scripting
p162 (p153 [no fix] Return
) "Keep track of your Return
function uses. The Return
function is inherently dangerous – remember that it will stop anything in that script below that line from being executed. Use it, but use it sparingly."
Return
should optimally be used as much as possible. While it is true that forgetting where you have used Return
will make scripting harder for you, Return
is what makes scripts efficient. It should always be used at the top for any conditions under which the script is not supposed to run, especially on disabled objects (being disabled does not stop the rest of the script from running every frame!). Even if lines are inside a condition that is false, the compiler will still read them all at runtime unless there is Return
above them. While "reading" the lines that are meant to be skipped is not as slow as truly running them, it is still slower than using Return
.
Cleaning up your mod
p163 "TESCS will remember you looked at the things if you moved them the slightest bit in the game world, or if you ever hit an “OK” button":
that is because when editing the properties of an object instance in a cell, you must click Cancel to avoid editing the object definition.
The TESAME link is dead. http://mw.modhistory.com/download-95-5289
The Enchanted Editor link is dead. http://mw.modhistory.com/download-37-1662
Keep in mind that Enchanted Editor is reported to sometimes corrupt large files by failing to save them completely; saving several times and making sure the size of the saved file is correct seems to avoid the issue for users.
The "change indicators found in the details tab" such as DIAL
and INFO
are not exclusive to TESAME and there are more than the list given there; they are TES3's data record types.
On References Persist
p165, "Indirect" referencing by functions: "Some functions may be exceptional in this respect.": besides GetDistance
, the other function is AiActivate
. "Direct" referencing is not only RefObject->Enable
, but also RefObject.Variable
in Set lines or in conditions:
Set RefObject.Variable to ... if ( RefObject.Variable ... )
See p29 "Referencing variables on other objects and scripts" and its comments here.
Sound file formats
p142: naturally the formats are the common 22050 Hz / 44100 Hz, not kHz.
p143: missing one of the special globals listed on p211 – Float "TimeScale" (30)
. Sets the ratio of real-time/game-time.
Define random Actor movement
Idle8
is supposed to be "pensive", not heartburn.- "Human" (not beast) female: also has a different
Idle4
, without shaking. - "Khajiit female" – is all beasts and not female specific.
GetArmorType + dialogue results or Choice
GetArmorType
seems to generally fail and return -1 in dialogue results if it is in a condition --If ( player->GetArmorType, 0 == -1 )
always passes.- On certain systems
GetArmorType
may generally fail in dialogue results and return -1 (why? helms quickly switched before dialogue seem to permanently trigger the issue). On other systems it may fail in dialogue results only if: it is in anIf
condition, or if it comes after aChoice
function, but not if it comes before theChoice
(why?). The bug does not seem to happen with other functions likeGetWeapon
orGetItemCount
.
Displaying variables and text defines in a message box
^PCRank
and ^NextPCRank
belong in the same list as ^Faction
and ^Rank
, which as noted later will not work – they cannot get a speaker's faction in script messageboxes, even if a faction NPC is explicit or implicitly targeted by its local script.
Adding a dialogue topic
"If another mod adds a topic with the same ID" is not a risk for hyperlinks; "an ID of which your topic's ID is a subset" is a risk as noted.
Topic hyperlinks are not retroactive: a topic will not be linked in a dialogue line if no entry was available before that dialogue line's results (Example: if there is no available entry before a Journal ID >= 10 condition, the topic will not be hyperlinked in the dialogue line that gives Journal ID 10 for the first time in its results).
Topics added in greeting results with Addtopic
will not appear in the topic list immediately (Example: see NPC M'Aiq the Liar's first greeting: the topics it adds only appear after a new dialogue frame; a new frame happens when a topic is used or when he is greeted a second time; a new frame can be forced with Forcegreeting
).
Equip and beast races
The Equip
function bypasses beast race restrictions on NPCs, and can make them equip items that take the head or feet slots (equipment changes apply out of MenuMode
). It can also bypass them on the player if it's used outside of MenuMode
AND is not the first Equip
function called on the frame (first AddItem
and Equip
another item, then Additem and Equip the head/foot-slot item).
Equipment sharing and other companion functions
set companion to 1
also enables inventory sharing if the creature is activated while knocked out: without sneaking, activating the creature when its fatigue is <= 0 will directly open its inventory.
Soul trap detection and GetEffect
, GetSpellEffects
GetEffect, sEffectSoultrap
only returns 1 if the player had a soulgem in their inventory when the spell hit its target. It doesn't matter whether the soulgem was empty or filled, or large enough to contain the soul or not, or if it is removed after the spell hit its target. With no soulgem in inventory the function always returns 0.
Right eval, Left eval errors
MSFD does have comments on them, but the exact errors are not searchable in the document because it is missing spaces; search for "RightEval", "LeftEval".
"Left" or "Right" is the side of the statement that is not accepted by the compiler, for instance to the left or right of the == sign. You may not see this error when compiling in the CS but it will come up when running the script in the game. Besides functions declared as variables, another example of that error is when the compiler gets the wrong object type for a function (common when the same ID is accidentally shared by a NPC and a script, or a NPC and a journal).
Some bugs (features) that MSFD warns against, but that are fixed by the most recent versions of the MCP
CellChanged
not returning 1 for scripted teleporting or magic teleporting is fixed by MCP.- "
CellChanged
doesn't always trigger, even if the player enters the cell via a normal teleport door": this comment likely referred to what happens when entering an interior through a load door that had a script withOnActivate
on it, a known bug with the Vivec Arena (this is also fixed by MCP). RemoveItem
subtracting weight from the character's encumbrance even if the amount of items is not in the character's inventory (fixed by MCP; the hack of removing negative weight items can no longer increase encumbrance).PlayGroup
,LoopGroup
being unreliable and having different animation on upper body and lower body is fixed by MCP.- Random function returning up to 1100 for specific values (65... 84) or crashed for multiples of 256 is fixed by MCP.
PlaceItem
,PlaceItemCell
: "placed NPC disappeared if I saved and reloaded before going to the cell they were placed in" is fixed by MCP "PlaceItem fix
"- Testing player blight disease ("Any time the current weather is blight, the player will have blight diseases added to them without any effects"... "make the player effectively immune to those diseases") is fixed by MCP.
- The 34th local variable is fixed by MCP "
Script expression parser fix
."