Skip to content

Getting Started⚓︎

Introduction⚓︎

The Skill Framework API allows modders to create and manage custom skills in OpenMW.

The framework is designed to be "plug-and-play". You just need to tell it about your skill and when to add XP. The framework's built-in handlers will take care of:

  • Calculating progress and level-ups.
  • Adding to the player's attribute increases on level-up.
  • Playing the "skill raise" sound and showing the on-screen message.
  • Integrating with Stats Window Extender to show your skill in the menu.

This guide will show you how to register and use a basic skill.

For a full, detailed breakdown, please see the API Documentation.

Setup⚓︎

This guide assumes you have both of the following frameworks installed:

  • Skill Framework: Provides the core mechanics for tracking XP and leveling.
  • Stats Window Extender: Required to display your skill in the character menu. Not a hard requirement, but highly recommended.

Make sure your mod loads after both of these frameworks. The correct load order is:

  1. Stats Window Extender
  2. Skill Framework
  3. [skill mods]

The API is accessible from the local scope of all non-creature actors. In a player/NPC script:

local API = require('openmw.interfaces').SkillFramework

Registering a basic skill⚓︎

To register a skill, call the registerSkill function in a local script on each actor the skill should be available to.

This can be done in either the script body, or an onInit/onLoad handler.

Here's an example for a "Fishing" skill:

local API = require('openmw.interfaces').SkillFramework
local core = require('openmw.core')

local l10n = core.l10n('MyMod')
local skillId = 'mymod_fishing' -- Recommended to namespace your ID to avoid mod conflicts
local useTypes = {
    CastRod = 1,
    CatchFish = 2,
    CatchTreasure = 3,
}

API.registerSkill(skillId, {
    -- Required: the in-game name
    name = l10n('skill_fishing_name'),

    -- Optional: other properties
    description = l10n('skill_fishing_desc'),
    icon = { fgr = "icons/MyMod/fishing.dds" },
    attribute = "strength",
    specialization = API.SPECIALIZATION.Combat,
    skillGain = {
        [useTypes.CastRod] = 0.5,
        [useTypes.CatchFish] = 3.0,
        [useTypes.CatchTreasure] = 10.0
    },

    -- Optional: properties for compatibility with other mods
    modIntegration = {
        statsWindow = { -- Stats Window Extender
            -- This will group the skill under a "Nature" subsection in the stats menu.
            subsection = API.STATS_WINDOW_SUBSECTIONS.Nature
        },
    }
})

Performing skill checks⚓︎

The API provides helper functions to calculate skill and fatigue factors based on vanilla formulas. You can use these to calculate success chances for skill checks:

local fishDifficultyRating = 50 -- a hard-to-catch fish
local fishingRodQuality = 1.2 -- a better rod gives a bonus
local statFactor = API.calcStatFactor(skillId)
local fatigueFactor = API.calcFatigueFactor()
local successChance = statFactor * fatigueFactor * fishingRodQuality - fishDifficultyRating
if math.random(0, 100) < successChance then
    -- Caught the fish!
end

Granting skill XP⚓︎

Whenever an actor performs an action, call skillUsed with the skill's ID and a SkillUseOptions table.

API.skillUsed(skillId, {
    useType = useTypes.CatchFish
})
In the options table, you may override the default XP gain by either specifying a skillGain value, or by setting scale to a multiplier.

For example, if you want to award XP based on the value of the caught fish:

local caughtFish = ...
API.skillUsed(skillId, {
    skillGain = caughtFish.value / 10,
    useType = useTypes.CatchFish -- will be overriden by skillGain, but you should still specify this in case a handler uses it.
})

Reading or changing skill stats⚓︎

getSkillStat will return the actor's SkillStat for the passed skill ID.

All fields of this table can be written to, except for modified, which is recalculated automatically based on the other values.

local fishingStat = API.getSkillStat(skillId)
fishingStat.base = 15
fishingStat.modifier = 0
print(fishingStat.modified) -- Prints 15
fishingStat.modifier = 20
print(fishingStat.modified) -- Prints 35

Note that changing a skill's base level in this way will skip most of the relevant handlers. In most cases, you should use skillUsed or skillLevelUp instead.

Forcing a level up⚓︎

As noted above, you should not modify getSkillStat().base to grant a level, as this will skip the level-up logic.

To manually increase a skill by a level (for example, as a quest reward), use the skillLevelUp function. This will run all the correct handlers, grant level-up progress, play sounds, and show UI messages.

-- Simulate a long day of fishing - grant multiple levels
API.skillLevelUp(skillId, API.SKILL_INCREASE_SOURCES.Usage, 3)

-- Skill loss in jail; will decrease the skill's level
API.skillLevelUp(skillId, API.SKILL_INCREASE_SOURCES.Jail)

Registering skill books⚓︎

The API allows you to easily register books that will grant levels in your skill when read. You just need to specify the book's record ID and the skill ID, and the framework will handle the rest.

-- "Fishing for Dummies"
API.registerSkillBook('my_bookskill_fishing1', skillId)

You can also specify additional properties such as how many levels to grant (1 by default), and custom logic to determine if a book is allowed to grant its skill.

-- "Master's Guide to Sheogorad Spearfishing"
API.registerSkillBook('my_bookskill_fishing2', skillId, {
    skillIncrease = 5,
    grantSkill = function()
        local failMsg = "The advanced concepts in this book elude you." -- optional
        local canRead = API.getSkillStat(skillId).base >= 50
        return canRead, failMsg
    end
})

Registering modifiers⚓︎

You can register base level modifiers based on race and class, as well as dynamic modifiers that will be recalculated continuously.

API.registerRaceModifier(skillId, 'argonian', 10)
API.registerClassModifier(skillId, 'scout', 5) -- Will only work for preset classes
-- Change fishing skill's modifier based on equipped gear
API.registerDynamicModifier(skillId, 'fishing_gear', function()
    local equipment = types.Actor.getEquipment(self)
    local hat = equipment[types.Actor.EQUIPMENT_SLOT.Helmet]
    local boots = equipment[types.Actor.EQUIPMENT_SLOT.Boots]

    local totalMod = 0

    if hat and hat.recordId == 'fishing_hat' then totalMod = totalMod + 10 end
    if boots and boots.recordId == 'fishing_boots' then totalMod = totalMod + 5 end

    return totalMod
end)

MWScript integration⚓︎

The framework simplifies the process of syncing your custom skill's stat with MWScript, so you can use it in things like scripts and dialogue filters.

The process is simple:

  1. Define your global variable in the Construction Set
  2. Call bindGlobal with the global's name and your skill ID

That's it. Whenever the skill's modified value changes, the bound global will automatically be set to that value.

Custom handlers⚓︎

The framework automatically handles skill increase and leveling logic. If you want to add your own logic or even override the defaults, you can use handlers.

For example, to run custom code when your skill levels up, you can add a skillLevelUp handler:

local function myLevelUpHandler(id, source, options)
    if id:lower() == skillId:lower() then
        print("My fishing skill just leveled up!")
    end
    -- Returning false will skip other handlers, including the built-in level up handler.
end

API.addSkillLevelUpHandler(myLevelUpHandler)

The following handlers are available:

For more information, see their respective documentation.