Scripting
Scripting is a vital part of quest development. Scripts update the journal, make items appear and disappear, play sound effects, and lots of other things.
The language Morrowind's scripts are written in is usually referred to as "mwscript". It is a conceptually simple, limited language designed by Bethesda to meet their questing needs. Hence, it is easy to pick up even for non-programmers. Unfortunately, the version of the language implemented in Morrowind.exe is notoriously buggy and inefficient, as attested by various MWSE and OpenMW developers.
Most of the below page will give you a general understanding of how mwscript works. However, especially with common tasks, it is often easiest to start with a working script and modify it to your needs. For that, check the Examples section below. Also, bear in mind that Tamriel_Data contains many pre-made scripts or variables to solve common problems.
In addition to reading this page, there are also many scripting-related tips and pitfalls listed on the Construction Set Tips page!
Also, when making scripts for NPCs, check the extra requirements in the NPC and Creature Guidelines.
Tutorials and Resources
Note that both these resources are known to contain errors. Either due to copying faulty information from the Construction Set's Help file, or due to Bethesda patching the game.
- Morrowind Scripting for Dummies – the most comprehensive guide to
mwscript
. Make sure to cross-reference the errata on the linked page. - UESP Morrowind scripting reference
How Does mwscript Work?
Basic Structure
A script is composed of several components.
Begin [Script Name] [Local Variables] [Expressions and Conditionals] End
Scripts are case insensitive. This means that Begin
is equivalent to BEGIN
or beGin
.
Begin/End
Every script must start with Begin [Script Name]
and end with End
. [Script Name]
is a valid name with a maximum length of 31 characters. For example to create an empty script named "my_script" enter
Begin my_script End
into the Construction Set's Script window and press Save.
Be aware that a script's name should only ever contain alphanumeric characters (A-Z, 0-9) and underscores. While other characters are possible, they should not be used in these projects.
Note that anything after the final End
is discarded. Some scripts end with End [Script Name]
, but this is not meaningful.
Names
A name is an identifier composed of either an unquoted string of alphanumeric characters, underscores; or a quoted string.
For example, test_123
is a valid name. test123
is also valid, but test 123
is not because it contains a space. If a space is desired, the name must be surrounded by quotes. For example, "test 123"
.
Note that .
and -
can sometimes be used in unquoted strings, but to prevent ambiguity and unexpected errors it is wiser to never uses .
and to always quote names containing -
.
Local Variables
A script's name is followed by zero or more local variable declarations, each on its own line. A variable is a named numeric value. Scripts often increment, decrement, and compare variables. Dialogue can also be filtered to specific variable values. There are three variable types: long
, short
, and float
. A local variable declaration is composed of a type followed by a name. We can, for example, add a variable of type "short" named "my_variable" to our previously empty script:
Begin my_script short my_variable End
All variables have an initial value of 0. It is important to realize that mathematical operations on two types will always return one of those types. So 3 / 2
will result in 1
if the input values were longs or shorts, but 1.5
if one or both of them were floats.
Note that while technically local variable declarations need not appear before all expressions and/or conditionals, it is good practise to do so as all variables are available throughout the script regardless of where they were declared and it increases legibility. Users familiar with JavaScript might recognise this behaviour as being similar to hoisting.
Long
A long is a 32 bit signed integer. This means it is a whole number between -2,147,483,648 and 2,147,483,647. This is the least common of the three types as most variables do not need to store values that large. When storing the player's gold count however, it is vital.
Short
A short is a 16 bit signed integer. This means it is a whole number between -32,768 and 32,767. This is the most common of the three types.
Float
A float is a 32 bit IEEE 754 floating point number. While the details of this are extremely complicated, it is enough to understand that it can contain numbers with decimal places.
Timers are one of the most important reasons for using floats as both the in-game clock (in hours) and real time (in seconds) are expressed using floats. Beyond that an object's position in the world is expressed as three floating point values, on the X, Y, and Z axis. Likewise, a player's stats are represented using floating point numbers (it is possible to have 30.5 health for example, though the interface will only show a rounded value.)
Special Variables
While a variable can have any valid name you desire, there are several special names. If your script declares a variable with one of these names, the engine may change its value at certain times.
Name | Description |
---|---|
Companion | Not modified by the engine, but setting it to 1 enables the Companion Share menu for the actor the script is attached to |
OnPCAdd | Set to 1 when the item the script is attached to is picked up by the player |
OnPCDrop | Set to 1 when the item the script is attached to is dropped by the player |
OnPCEquip | Set to 1 when the item the script is attached to is equipped by the player |
OnPCHitMe | Set to 1 when the actor the script is attached to is hit by the player |
OnPCRepair | Set to 1 when the item the script is attached to is repaired by the player |
PCSkipEquip | Not set by the engine, but setting this to 1 prevents the item the script is attached to from being equipped by the player |
StayOutside | Not set by the engine, but setting this to 1 causes player followers to stay outside when the player enters an interior |
Global Variables
Global variables are created in the Construction Set, outside the Script window. Like local variables, they can be one of the three types above. Unlike local variables, they can be assigned an initial value other than 0. Global variables can be used in much the same way as local variables. The biggest difference is that they are "globally" available to all scripts.
Expressions
An expression is a piece of code that does something. The most common forms of expressions are updating a variable, calling a function, or comparing a value.
Setting Variables
The basic syntax for changing a variable's value is set [Variable Name] to [Expression]
. Where [Variable Name]
is the name of the local or global variable to change, and [Expression]
is either a numeric value or an expression resulting in a numeric value. We could, for example, add set my_variable to 123
to the script above to change the value of "my_variable" to 123. To make a simple counter that increments "my_variable" by 1 every time it runs we can use an expression as follows:
Begin my_script short my_variable set my_variable to ( my_variable + 1 ) End
Here the expression my_variable + 1
takes the current value of "my_variable", adds 1, and returns the new value for use by set ... to
. Expressions are usually wrapped in parenthesis. As in mathematics, parenthesis affect operator precedence.
There are 10 operators. Each is used in the form [left hand value] [operator] [right hand value]
, i.e. they are all binary operators. It is possible to combine multiple operations into a single expression. For example, set my_variable to ( ( my_variable + 1 ) * 2 )
, features both addition and multiplication. This gets more interesting with multiple different variables.
Operator | Name | Description | Inverse |
---|---|---|---|
+ |
Addition | Adds the value on the left and the value on the right, returning the sum | -
|
- |
Subtraction | Subtracts the value on the right from the value on the left, returning the difference | +
|
* |
Multiplication | Multiplies the value on the left with the value on the right, returning the product | /
|
/ |
Division | Divides the value on the left by the value on the right, returning the fraction (nb: division by 0 is not allowed) | *
|
== |
Equality | Compares the value on the left to the value right, returning 1 if they are equal and 0 if they are not | !=
|
!= |
Inequality | Compares the value on the left to the value right, returning 0 if they are equal and 1 if they are not | ==
|
> |
Greater than | Returns 1 if the value on the left is greater than the value on the right, 0 if it is not | <=
|
>= |
Greater than or equal | Returns 1 if the value on the left is greater than or equal to the value on the right, 0 if it is not | <
|
< |
Lesser than | Returns 1 if the value on the left is lesser than the value on the right, 0 if it is not | >=
|
<= |
Lesser than or equal | Returns 1 if the value on the left is lesser than or equal to the value on the right, 0 if it is not | >
|
Functions
A function is a special name that causes the engine to do something. Functions may take 0 or more arguments and they may return a value. Both the UESP and Morrowind Scripting for Dummies provide more detailed information on which functions exist. Note that both occasionally contain mistakes or notes that do not apply to a fully patched game.
A function call looks like [Function Name] [Arguments]
. Where [Function Name]
is the name of the function and [Arguments]
is a list of quoted values (i.e. strings), numeric values, and expressions; separated by spaces. So we can expand our script to toggle god mode every time it is executed as follows:
Begin my_script short my_variable set my_variable to ( my_variable + 1 ) tgm End
In this case, the function's name is tgm
and it has no arguments. Alternatively, we could make the script teach a spell to its implicit target:
Begin my_script short my_variable set my_variable to ( my_variable + 1 ) AddSpell "fire storm" End
In this case the function's name is AddSpell
and the function's argument is a single ID: "fire storm"
. Some functions need more arguments than others, the examples above already show 0 and 1 arguments respectively, but AddItem "iron dagger" 5
has two arguments (the item to add, and the number of items to add) and the AiWander
function takes up to 13 arguments. Sometimes some or all of a function's arguments are optional, usually this means any omitted arguments are treated as if they were 0, but this varies from function to function.
Nota bene: It is not unusual to see function arguments separated by commas. This is pointless as there is no vital difference between commas and spaces. Prefer using only spaces.
If a function returns a value, that value can be assigned to a variable. For example, to make "my_variable" hold the implicit target's current level, we can write the following:
Begin my_script short my_variable set my_variable to ( GetLevel ) End
Here, GetLevel
is a function taking 0 arguments and returning a numeric value.
Conditionals
It is usually not desirable to execute certain lines of code all the time or even more than once. This means these lines should be conditionally executed. Conditional lines come in two flavours:
if [Expression] [Expressions and Conditionals] elseif [Expression] [Expressions and Conditionals] else [Expressions and Conditionals] endif
and
while [Expression] [Expressions and Conditionals] endwhile
Of these two, the former is the most common. In these examples, the [Expression]
following if
, elseif
, or while
represents a condition. If such a condition results in a value other than 0 (i.e. the condition is true), the code directly below it is executed until a matching elseif
, else
, endif
, or endwhile
is encountered. In the case of while
, the code is continuously executed until the condition return 0 (i.e. false.) A set of lines (or block of code) starting with an if
statement and ending with an endif
can have 0 or more elseif
statements following the if
, followed by 0 or 1 else
statements. elseif
statements are evaluated when none of the preceding if
or elseif
statements were true, likewise the code following else
is executed if none of the preceding if
or elseif
statements proved true.
Combining this knowledge with our earlier scripts lets us create a script that, if its target's level is more than 1, gives it the "fire storm" spell:
Begin my_script short my_variable set my_variable to ( GetLevel ) if ( my_variable > 1 ) AddSpell "fire storm" endif End
Likewise, we can double the target's gold using a while
loop:
Begin my_script short my_variable set my_variable to ( GetItemCount "gold_001" ) while ( my_variable > 0 ) AddItem "gold_001" 1 set my_variable to ( my_variable - 1 ) endwhile End
Note the addition of set my_variable to ( my_variable - 1 )
to decrement "my_variable" by 1 on every iteration of the loop. Removing this line would result in AddItem
being executed ad infinitum (provided the target has more than 0 gold to start with) freezing the game indefinitely.
Also note that each line inside the if
or while
block is indented with a tab. This is not strictly necessary, but it greatly improves legibility especially when nesting conditions, as should be obvious here:
Begin my_script short my_variable set my_variable to ( GetLevel ) if ( my_variable > 10 ) AddSpell "fire storm" elseif ( my_variable > 5 ) set my_variable to ( GetItemCount "gold_001" ) while ( my_variable > 0 ) AddItem "gold_001" 1 set my_variable to ( my_variable - 1 ) endwhile endif End
If the target's level is over 10, this script teaches it the "fire storm" spell, otherwise if its level is over 5, it doubles the target's gold. If the target's level is lesser than or equal to 5, nothing happens.
Comments
A line may contain a ;
, anything after the first semicolon is treated as a comment. This means it is not executed. Comments are used to explain code and to remove lines without outright deleting them.
Begin my_script short my_variable set my_variable to ( GetLevel ) if ( my_variable > 10 ) ; AddSpell "fire storm" elseif ( my_variable > 5 ) ; Double our gold! set my_variable to ( GetItemCount "gold_001" ) while ( my_variable > 0 ) AddItem "gold_001" 1 set my_variable to ( my_variable - 1 ) endwhile endif End
Here we see that the script no longer teaches the "fire storm" spell, instead doing nothing when the target's level is greater than 10. It also adds an explanation of what the while
loop does.
Nota bene: it is not possible to quote a semicolon. The expression MessageBox "abc ; 123"
is equivalent to MessageBox "abc
.
References
A large number of functions require a reference to an instance of an object to work. The above examples sometimes use the word "target" to denote this reference, i.e. the target of the script.
Explicit References
Explicit references come in two flavours: one for accessing local variables, and one for calling functions.
Explicit Function Calls
To call a function with an explicit reference use the following syntax: [Reference Name]->[Function Name] [Arguments]
. So for example:
player->SetHealth 100
Note that this is the same syntax used in the Functions section with the explicit reference's name and ->
prepended. This line sets the player's health to 100.
Nota bene: a line can only use one explicit reference. So while player->SetHealth ( fargoth->GetHealth )
may appear valid, the reference to "fargoth" will silently be replaced with "player" when Morrowind.exe executes the line.
[Reference Name]
should be the ID of a unique object. If multiple objects share the same ID (as is the case with most items and NPCs like guards), you may not end up targeting the correct instance.
References Persist
To tell Morrowind.exe that an object can be used as the explicit target of a script, "References Persist" must be checked on that object in the Construction Set. NPCs and Creatures do not have this check box and will work without. Failure to check the box on an object used in a script will result in an error pop-up when starting the game.
Accessing Another Script's Local Variables
It is possible to get and set local variables contained in another script using explicit references. The syntax to reference a variable in another script is [Reference Name].[Variable Name]
. Where [Reference Name]
can be either the ID of a unique instance (as with functions), or the ID of a global script. For example, the NPC "fargoth" has a script "LocalState" that declares a short
variable called "state". To get and set that value, we can do the following:
Begin my_script short my_variable set my_variable to ( fargoth.state ) if ( my_variable > 10 ) fargoth->SetHealth 100 else set fargoth.state to ( my_variable + 1 ) endif End
If instead of an instance ID we provide a script ID, we access the local variables of the globally running version of that script. The syntax is otherwise the same:
Begin my_script short my_variable set my_variable to ( LocalState.state ) if ( my_variable > 10 ) fargoth->SetHealth 100 else set LocalState.state to ( my_variable + 1 ) endif End
Be aware that the variables in the "LocalState" script running on "fargoth" can have different values from the ones in the globally running script. Any newly spawned instance of "fargoth" will inherit its values from the global "LocalState" script, meaning "state" may not have an initial value of 0.
Implicit References
Any script can have an implicit reference. For local scripts this is the object they are attached to. Global scripts either do not have an implicit reference, or use the reference the StartScript
function was called with.
Implicit references provide a way to run scripts on non-unique instances without running into the problem of not knowing which instance is going to be affected.
Nota bene: a global script with an implicit reference to an object without a RefNum (i.e. an object not placed using the Construction Set, for example one spawned by a script or levelled list) lose their implicit reference when a save is loaded. Because of this it is wise to keep global scripts short and to the point, and to prefer putting logic inside local scripts.
Execution Model
All active scripts run every frame. This means that at 60fps, every active script is executed 60 times per second. Because of this it is very easy for an inefficient script to seriously impact performance. Any script can be a local or global script, potentially both at the same time. The only difference is the way in which they are executed. All active scripts are executed from top to bottom, starting at the top on each new execution.
Local Scripts
A local script is a script assigned to an object (or record) in the Construction Set. A local script is active if the object it is attached to is within the active grid. When the player is inside an interior cell that means all objects in that cell. When the player is outside, it means every objects in the 3x3 grid centered on the player. If multiple objects in the active grid have the same script, the script is executed as many times as there are objects with that script. Each local script has its own variable values.
Global Scripts
A global script is a globally running instance of a script, which is to say it runs regardless of the player's location. There can only be one active global instance of a script. A global script has its own local variable values, separate from any locally running instances of the same script. Any newly spawned objects with a local script matching the global script, will inherit their initial variable values from the global script.
Start Scripts
It is possible to add scripts to the list of Start Scripts in the Construction Set. Doing so will cause the script to be started as a global script whenever a new game is started or a save is loaded. Note that this means that a stopped script will restart when the player loads a save.
StartScript And StopScript
StartScript
and StopScript
are functions that start and stop global scripts. There is also the ScriptRunning
function which returns 1 if the script passed as its first argument is running globally.
A script started using StartScript
has an implicit reference to the reference the function was called with. This means that player->StartScript my_script
will start the "my_script" script with an implicit reference to the player. If a global "my_script" started this way then calls StartScript LocalState
(without an explicit reference), the "LocalState" script will also be started with an implicit reference to the player because that was "my_script"'s implicit reference. As such it is not possible to start a script without an implicit reference from a script that has an implicit reference.
A script stopped using StopScript [Script Name]
will not be executed until it is started again. Note that it is not possible to stop local scripts using this function. The only way to stop a local script is to cause the instance it is attached to, to be unloaded. This can be accomplished by moving away from the instance (i.e. causing it to be outside the active grid), by clicking the Dispose of Corpse button on the instance, or by using the SetDelete
function.
Dialogue Scripts
A dialogue response (or INFO record) can have a result script. A result script, unlike a regular script, is composed solely of expressions, conditionals and comments. The result script is executed with an implicit reference to the actor who said the line. Result scripts can use any local variables declared in the actor's script.
Return
Return
is a function that halts further execution of a script. Which is to say any lines following the Return
will not be executed. Note that this does not stop the script, it will start execution again from the top on the next frame. Due to the inefficient way Morrowind.exe executes scripts, strategically adding Return
statements can significantly increase a script's performance. For example, the following two scripts are identical in what they accomplish, but the second is more efficient.
Begin my_script if ( MenuMode ) SetMagicka 1 else SetFatigue 100 endif End
Begin my_script if ( MenuMode ) SetMagicka 1 Return endif SetFatigue 100 End
Obviously the effect of this is more pronounced in larger, more complex scripts.
Examples
Triggering journal updates
Triggering on death conditions
Trigger journal when an NPC dies:
;Local script on the NPC or creature Begin TR_m3_OnDeathExample short TR_Map if ( OnDeath ) ;update journal here ;add conditions if necessary, ;like checking that the quest to kill the NPC was started endif if ( TR_Map == 3 ) return endif set TR_Map to 3 End
Trigger journal once several NPCs are dead:
;Local script on each NPC, the same script, only one is needed Begin TR_m3_MultiOnDeathExample short TR_Map if ( OnDeath ) If ( GetDeadCount "TR_m3_NPCExample1" > 0 ) If ( GetDeadCount "TR_m3_NPCExample2" > 0 ) If ( GetDeadCount "TR_m3_NPCExampleX" > 0 ) ;update journal here ;add conditions if necessary, ;like checking that the quest to kill the NPCs was started Endif Endif Endif endif if ( TR_Map == 3 ) return endif set TR_Map to 3 End
Trigger journal once several creatures are dead:
;Local script on the creature ID Begin TR_m3_CreaturesOnDeathExample short TR_Map if ( OnDeath ) If ( GetDeadCount "TR_m3_CreatureExample" >= 3 ) ;3 of the creature to kill ;update journal here ;add conditions if necessary, ;like checking that the quest to kill the creatures has started Endif endif if ( TR_Map == 3 ) return endif set TR_Map to 3 End
Triggering on timed conditions
Wait 24 hours before triggering something:
;Global script, start in dialogue results or another script with Startscript, terminates itself Begin TR_m3_24HoursExample short DayTarget float HourTarget if ( DayTarget > DaysPassed ) return elseif ( DayTarget == DaysPassed ) if ( GameHour <= HourTarget ) return endif endif if ( DayTarget == 0 ) set DayTarget to ( DaysPassed + 1 ) set HourTarget to ( GameHour ) return endif ;24 hours have passed, update journal here StopScript "TR_m3_24HoursExample" End
Wait a week before triggering something:
;Global script, start in dialogue results or another script with Startscript, terminates itself Begin TR_m3_7DaysExample short DayTarget if ( DayTarget > DaysPassed ) return endif if ( DayTarget == 0 ) set DayTarget to ( DaysPassed + 7 ) return endif ;7 days have passed, update journal here StopScript "TR_m3_7DaysExample" End
Triggering events per quest stage
Disable a NPC or object until a quest starts:
;Local script on the object ;If it's a NPC, add the generic NPC variables and script like in T_ScNpc_Mw_Map3 ;If it's lootable, don't forget Morrowind is an open world that doesn't make things magically pop up only when the player needs them, instead your quest logic often needs to account for the object being found first ;If it's a static object with collision, don't forget you also need to change its position Begin TR_m3_QuestEnableExample if ( GetJournalIndex "TR_m3_JournalExample" < 10 ) if ( GetDisabled ) return else Disable endif elseif ( GetDisabled ) Enable endif End
Make a NPC in exteriors leave after a quest is finished:
;Local script on the NPC ;Unless they're using magic, you may not disable NPCs in front of the player Begin TR_m3_QuestDisableExample short TR_Map short control short controlQ float GameHourCheck if ( TR_Map != 3 ) set TR_Map to 3 endif if ( GetJournalIndex "TR_m3_JournalExample" < 100 ) return elseif ( GetDisabled ) ;don't use "SetDelete 1" here unless the NPC instance never needs to be referred to again return elseif ( CellChanged ) if ( GetInterior ) Disable return else ;do not disable when crossing exterior cell borders set GameHourCheck to ( ( GameHour - GameHourCheck ) * 30 / TimeScale ) if ( GameHourCheck < 0 ) set GameHourCheck to ( -1 * GameHourCheck ) endif if ( GameHourCheck < 0.01 ) if ( GetDistance, player < 6500 ) return endif endif Disable endif endif set GameHourCheck to GameHour End
Book scripts
Perform an action when the player opens a book or a scroll:
Begin your_script_name short OnPCEquip short PCSkipEquip set PCSkipEquip to 0 ; any actions that might end the script must be placed lower than this line ; Activating from the inventory if ( MenuMode ) ; the vanilla engine fails to set PCSkipEquip under certain ; conditions, so we must do it ourselves for ; OnPCEquip to work properly set PCSkipEquip to 1 if ( OnPCEquip ) ; place actions for when the player opened the book from the inventory here set OnPCEquip to 0 set PCSkipEquip to 0 ; OpenMW fails to open the book without this line Activate endif ; this return is important ; we must make sure OnActivate secton is only accessed outside of MenuMode ; or the vanilla engine fails to take the book if opened from the world return endif ; Activating in the world if ( OnActivate ) ; place actions for when the player opened the book from the world here Activate endif end
Tamriel_Data Premade Scripts
Enabling and disabling teleportation
Morrowind has two functions to enable or disable teleporting, aptly named EnableTeleporting
and DisableTeleporting
. These functions disable hard-coded teleportation spells (Divine and Almsivi Intervention, Recall), but can not block scripted teleportation (such as the Vampire amulets players can acquire, or Barilzar's Mazed Band). There is also no way to check if teleporation is disabled. This is where Tamriel_Data comes in with a teleportation "framework", essentially a single global variable named T_Glob_GetTeleportingDisabled
.
Ultimately, using it is very simple: T_Glob_GetTeleportingDisabled should be run in parallel to EnableTeleporting and DisableTeleporting. Set one where the other is set: 0 when teleporting is enabled and 1 when it is disabled.
How to set it
This example script disables teleporting on entering a certain cell. It is activated by a separate activator script and runs globally until it is shut off once the player leaves the cell:
Begin TR_m9_q_TelTerribleNoTeleport short teleportOff if ( teleportOff == 0 ) if ( GetPCCell "Tel Terrible" == 1 ) DisableTeleporting Set T_Glob_GetTeleportingDisabled to 1 set teleportOff to 1 endif endif if ( teleportOff == 1 ) if ( GetPCCell "Tel Terrible" != 1 ) EnableTeleporting Set T_Glob_GetTeleportingDisabled to 0 set teleportOff to 0 StopScript "TR_m9_q_TelTerribleNoTeleport" endif endif End
How to read it
This is an example script heavily cribbed from vanilla scripts - by equipping the amulet of the Draculus Clan, the player is supposed to be teleported back to the clan's lair. The script will fail silently:
Begin amuletDraculusScript short button short messageOn short reset short OnPCEquip if ( OnPCEquip == 0 ) set reset to 0 endif if ( reset == 1 ) return endif if ( OnPCEquip ) if ( reset == 0 ) if ( VampClan == 42 ) if ( PCVampire == 1 ) if ( T_Glob_GetTeleportingDisabled ) else Set OnPCEquip to 0 MessageBox "Would you like to return to Tel Terrible?" "Yes" "No" set messageOn to 1 endif endif endif endif endif if ( messageOn == 1) set button to GetButtonPressed if ( button >= 0 ) set messageOn to 0 endif if ( button == 0 ) Player->PositionCell, 2077.101, -785.586, -752, 270, "Tel Terrible, Vampire Lair" set reset to 1 elseif ( button == 1 ) set reset to 1 return endif endif End
Skipping time
The way to pass time should be more or less obvious: fade out, pass time, then fade in. Unfortunately, Morrowind does not actually have scripted time-skips. On one hand, while Morrowind does have "fade out, things happen, fade in" scripts, they do not pass any more time than the script takes to run. On the other hand, prison and travel times are hardcoded. In order to create a time skipping framework, Tamriel_Data contains a global and a script for this purpose.
The functionality is invoked by setting T_Glob_PasstimeHours
and then calling StartScript T_ScGlobalPassTime
in the Dialogue Result box or in the script. The script handles the fade out/fade in and calculates the correct day, month, and year according to the hours that have been set. As such, there is no need to further calling of the script: it handles everything.
Note: The script assumes that the corrected calendar is in use, which means that either using Morrowind Code Patch or OpenMW. As these are already requirements for using Tamriel Rebuilt and Project Tamriel, this should only come up if somebody tries to test this with a deliberately unpatched Morrowind.exe and skips into month 12.
Standardized speech skill checks
The topic of skill checks is something that comes up quite often. Tamriel Rebuilt contains several skill checks, of which quite a few are Speechcraft checks that unlock additional responses to multiple choice dialogue. Several kind of checks can be done, depending on situation:
- if you need the NPC to trust the player in order to reveal information or offer options, defer to Disposition,
- if the speechcraft skill is specifically used for unlocking dialogue or an interaction, use a Speechcraft check,
- if the player needs to actively convince the NPC, keep reading.
General Overview
While normal Disposition and Speechcraft checks can deal with many situations, for persuading NPCs (particularly in multiple-choice dialogue) a mere skill check alone is unintuitive and unimmersive, as this removes other possible factors (NPC disposition in particular).
For this reason, Tamriel_Data contains a scripted framework for unified Speech Skill Checks with v08 and later. It is invoked by simply calling StartScript T_ScSpeech_SkillCheck
in the Dialogue Result box and checking the persuasive power Global(s).
This script framework offers three kinds of speech skill checks: Sway (pure charisma), Haggle (bargaining), and Debate (convincing somebody with your smarts).
These checks are ran as opposed checks for both the PC and the NPC:
Option | = | + | + | Persuasive Power in Global |
---|---|---|---|---|
Sway | Speechcraft | Personality / 2 | Luck / 10 | T_Glob_Speech_Sway |
Haggle | Mercantile | Personality / 2 | Luck / 10 | T_Glob_Speech_Haggle |
Debate | Speechcraft / 2 | Intelligence | Luck / 10 | T_Glob_Speech_Debate |
The intermediate result is further multiplied by NPC disposition (ranging from 1.5 for 0, to 0.5 for 100, linear scaling) and by PC fatigue (ranging from 0.5 for 0, to 1 at max, with a SQRT curve to be more forgiving). The PC result is divided by the NPC result, leading to the value found in the corresponding Global.
This means that, if the NPC's disposition is 50, the PC's fatigue is full, and both PC and NPC have identical skills and attributes, the persuasive power is exactly 1.
If the NPC's disposition is 100, their skills and attributes need to be 1.5x as high as the players to reach a presuasive power of 1.
If the NPC's disposition is 0 and is not already engaging the player in combat, the PC's skills and attributes need to be 1.5x as high as theirs in order to reach a persuasive power of 1.
With a fatigue of 0, the PC's result will be half much as it could be. The closer to full fatigue the PC is, the less severe this penality will be.
In summary: the persuasive power, the final result of these opposed checks, is a value of the PC Speech Skill Check in relation to the NPC Speech Skill Check. This means that, for the PC, results of less than 1 are a loss and (equal and) greater than 1 are a success. The result also displays a measure of failure/success, allowing several different replies to be implemented.
Testing and Debugging
The downside of this script is that it can be a little unclear what an NPC's persuasive power will be from the Construction Set.
To alleviate this, a debug global is available in Tamriel Data, and an example ESP has been made available that can help you explore how the NPC and PC power values are constructed in-game.
The example ESP file with debug dialogue is available here!
The debug Global is set to 0 by default. Set it to higher values to gain dynamic feedback on how the script calculates its values. The dummy greetings enabled by setting the global to 3 are included in the example ESP file, while the other debug settings can be used without that additional file.
T_Glob_Speech_Debug | Result |
---|---|
0 | No debug messages |
1 | Lists final power values |
2 | additionally lists all relevant attributes |
3 | additionally enables dummy Greeting 0 entry for all NPCs |
Banking framework
Tamriel_Data contains a framework for banks that provide deposit and withdraw services. Scripts and items can be used to add new branches to existing banks or new banks altogether. See the specific Banking framework sub-page.