Scripting

From Project Tamriel Wiki
Jump to navigation Jump to search

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!

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.

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

An example of how to use the Tamriel_Data time-skip framework.

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.
Starting a Tamriel_Data speech skillcheck.
Checking a Tamriel_Data speech skillcheck.

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.

How the speech skillcheck debug looks like.

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.