Skip to main content
Abilities in Legends of Dota Redux are defined across several files: a Lua script for logic, KV data files for configuration, and registration in pregame.lua for any modifiers. Follow the steps below to add a new ability from scratch.
1

Create the Lua file

Add a new .lua file in src/game/scripts/vscripts/abilities/. Name it after the ability using snake_case, matching the ability’s internal name.
src/game/scripts/vscripts/abilities/my_new_ability.lua
If your ability uses a Lua-defined modifier, declare it with LinkLuaModifier at the top of the file. This registers the modifier class so the engine can find it at runtime.Here is an example from basic_stat_bonus.lua, which defines an intrinsic passive modifier:
abilities/basic_stat_bonus.lua
basic_stat_bonus = class({})

modifier_basic_stat_bonus = class({})

LinkLuaModifier("modifier_basic_stat_bonus", "abilities/basic_stat_bonus.lua", LUA_MODIFIER_MOTION_NONE)

function basic_stat_bonus:GetIntrinsicModifierName()
    return "modifier_basic_stat_bonus"
end

function modifier_basic_stat_bonus:IsPermanent() return true end
function modifier_basic_stat_bonus:IsHidden() return true end

function modifier_basic_stat_bonus:DeclareFunctions()
    return { MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, MODIFIER_PROPERTY_STATS_AGILITY_BONUS, MODIFIER_PROPERTY_STATS_INTELLECT_BONUS }
end

function modifier_basic_stat_bonus:GetModifierBonusStats_Strength()
    if self:GetCaster():PassivesDisabled() then return 0 end
    return self:GetAbility():GetSpecialValueFor("stat_bonus")
end

function modifier_basic_stat_bonus:GetModifierBonusStats_Agility()
    if self:GetCaster():PassivesDisabled() then return 0 end
    return self:GetAbility():GetSpecialValueFor("stat_bonus")
end

function modifier_basic_stat_bonus:GetModifierBonusStats_Intellect()
    if self:GetCaster():PassivesDisabled() then return 0 end
    return self:GetAbility():GetSpecialValueFor("stat_bonus")
end
For event-driven abilities (spells with OnSpellStart, kill events, etc.), you define plain functions without a class structure. See arcane_mastery.lua for a minimal example:
abilities/arcane_mastery.lua
function onSpellStart(event)
    local caster = event.caster
    local ability = event.ability

    if caster:HasModifier("modifier_custom_arcane_mastery") then
        caster:RemoveModifierByName("modifier_custom_arcane_mastery")
        ability:ApplyDataDrivenModifier(caster, caster, "modifier_custom_arcane_mastery", nil)
    else
        ability:ApplyDataDrivenModifier(caster, caster, "modifier_custom_arcane_mastery", nil)
    end
end

function onHeroKill(event)
    local caster = event.caster
    local cooldownReduction = event.ability:GetSpecialValueFor("cooldownReduction")

    for i = 0, caster:GetAbilityCount() - 1 do
        local ability = caster:GetAbilityByIndex(i)
        if ability ~= nil and ability ~= event.ability then
            local timeLeft = ability:GetCooldownTimeRemaining()
            ability:EndCooldown()
            ability:StartCooldown(timeLeft - cooldownReduction)
        end
    end
end
2

Register the ability in npc_abilities_custom.txt

Define the ability’s KV data in the custom abilities file. This is where you set ability type, behavior, damage type, special values, and references to the Lua script.The ScriptFile key points to your Lua file relative to vscripts/:
"my_new_ability"
{
    "BaseClass"         "ability_lua"
    "ScriptFile"        "abilities/my_new_ability.lua"
    "AbilityBehavior"   "DOTA_ABILITY_BEHAVIOR_PASSIVE"

    "AbilitySpecial"
    {
        "01"
        {
            "var_type"      "FIELD_INTEGER"
            "stat_bonus"    "5 10 15 20"
        }
    }
}
3

Add the ability to abilities.kv

src/game/scripts/kv/abilities.kv controls which abilities appear on the ability selection screen and what bracket (category) they fall into.Add your ability to the appropriate bracket. The brackets represent power tiers shown in the selection UI. Refer to the existing entries in abilities.kv to choose the right bracket for the ability’s strength.
"my_new_ability"    "1"
The value corresponds to the bracket number defined in abilities.kv. See the abilities.kv reference for the full list of brackets.
4

Add to bans.kv if needed

If your ability creates a problematic or unplayable combination with another ability, add a ban entry to src/game/scripts/kv/bans.kv.Most combination bans are now handled with ReduxBans flags directly in the ability’s KV data in npc_abilities_custom.txt, which is the preferred approach. The bans.kv file is used for special cases: group bans (where three or more abilities together are banned), or combinations that cannot be expressed as simple pairwise flags.See the bans.kv reference for the format.
5

Register modifiers in pregame.lua

If your ability defines one or more Lua modifiers with LinkLuaModifier, you must also require the ability file in pregame.lua so the modifiers are available during the pregame phase.In src/game/scripts/vscripts/pregame.lua, add a require call alongside the existing ability requires:
pregame.lua
require('abilities/my_new_ability')
This ensures LinkLuaModifier runs at startup and the engine can instantiate your modifier class.
Run npm run check before committing to verify your files pass the Prettier formatting check.

Build docs developers (and LLMs) love