Jordan Savant # Software Engineer

Scripting a MOD for The Elder Scrolls IV: Oblivion

This was my first foray into modding Elder Scrolls titles and I started with Oblivion mainly because I was actively replaying it and I had an idea. I was playing a battlemage and felt that there was balancing issues. But more on that later. For now I followed a tutorial on Scripting, because as a programmer scripting is what interests me, not so much 3D editing or world building elements.

Setup the TES Construction Kit

The entire Oblivion game can be edited with the TES Construction Kit.

Install, open and then File > Data > Oblivion.esm

The .esm files are the Elder Scrolls master files that contain all of the resources for the game.

The .esp files are the Elder Scrolls patch files that can make adjustments to the content in the esm files. This is not unlike DOOM WAD files that I explored.

When saving our mod with File > Save we must place it in the Data directory of our Installation folder. For me that was in steamapps.

TES Construction Kit UI

Our First Script: Riddle Chest

This came from a tutorial online and it covers some basic structure for scripts:

  1. Each script must have its name declared on the first line: ScriptName RiddleChestScript
  2. Event blocks must wrap code and trigger with in-game events such as: Begin OnActivate if attached to an object activated by the player
  3. These events essentially run on repeat based on their activation and are called BlockType
  4. This creates a lot of "do once" booleans to check if code has already been run: short doOnce

The goal of the tutorial was to attach a script that poses a riddle to the player in order for it to open.

Under Gameplace > Edit Scripts > New we can create scripts and the tite line becomes its name. From there it can be edited.

I created an object type script that lets us pose a riddle to the player on opening an object as a lock:

ScriptName RiddleChestScript

; This script locks an object behind a riddle
; If it is answered correctly it will open to reveal its contents
; If it is failed it will lock cast a curse attack and lock permanently

Short controlvar
Short button

Begin OnActivate
    if ( controlvar == -1 )
        MessageBox "I will suffer no fools."
    ElseIf ( controlvar == 0 )
        MessageBox "Voiceless it cries, wingless flutters, toothless bites, mouthless mutters. What is it?", "Bat", "Old woman", "Wind", "Wraith"
        Set controlvar to 1
    ElseIf ( controlvar > 1 )

; game play loop for player
Begin GameMode
    If ( controlvar == 1 )
        ; get which button option was last selected by the player
        Set button to GetButtonPressed
        If ( button == -1 )
            ; TODO not sure what causes this state...?
        ElseIf ( button == 2)
            ; running Activate here causes double overlay of UI, one for the message and one for the inventory
            ; so instead we activate it on the next gameplay loop
            MessageBox "Your answer was correct."
            Set controlvar to 2
            ; no more chances
            MessageBox "Your answer was wrong."
            ; punish them with a curse
            Cast Mg05FingerSpell15 Player
            Set controlvar to -1
    ElseIf ( controlvar == 2 )
        ; move to end state
        Set controlvar to 3

I went into the Object Window and under WorldObjects > Container > Clutter attached this script to the "Lectern" object. There happens to be a lectern on the desk of the Alchemist in Cheydinhall. Upon activating it we get:

Lectern Riddle UI

Making a Script Global

Scripts are of three types: Object, Quest or Magic Effect. Objects are attached to objects in the world and run when they are being updated as part of their object. A Quest script can run at all times, but since it is not a part of an object it has reference to less reference variables. This is fine because I want my script to be running on a continual basis.

To create a Global Script reference from TES Construction Kit Global Scripts.

This first version of the quest script will initalize some values when the script starts and set the current player level. Then if during gameplay the player level changes we can capture it.

ScriptName PRAE_OnMagicUp

short state
short lastLevel
short currentLevel

; Quest scripts run on 5 second intervals
Begin GameMode
    If ( state == 0 )
        Set currentLevel to Player.GetLevel
        Set lastLevel to currentLevel
        Message "level is %.0f" currentLevel
        Set state to 1
        ; on each update cycle poll our current level
        Set currentLevel to Player.GetLevel

    If ( currentLevel != 0 && lastLevel != currentLevel )
        Set lastLevel to currentLevel
        Message "level up detected lets add some magicka"

With the 5 second intervals it takes a second to apply the level detection routine. I think we could adjust this to a Begin MenuMode 1027 which detects if the level up menu runs.