Dota 2 lua scripting

The aim of this tutorial is to give an introduction to the scripting system the dota engine uses, and on giving some tips and tricks for developing.
Basic knowledge of OOP programming and lua is assumed.

For more tutorials on addons, or for lua scripting applied to existing (open-source) addons check the main tutorial page.

Please don't use notepad for this, at least get Sublime or notepad++.

This file does not contain any information about datadriven abilities, for that check out the wiki!

Also see the wiki for a list of all available lua functions and a list of all constants, as well as a list of engine events.

Table of contents

  1. The basics
  2. The core of your addon
  3. Extending the core
  4. Tips and tricks

The basics

We will be working in your game/dota_addons/your_addon/scripts/vscripts directory. When your addon is loaded the dota 2 engine executes one file here, namely addon_game_mode.lua, this means that whatever you want to use has to originate from that file, but it does not prevent you from using 'requires' to include other lua files. Separating your code into multiple lua files is a great idea for structuring your code. Usually you will have a few libraries/utilities files and a main addon .lua.

To include another lua file add this to the top of your file:

require("Library") --Including Library.lua

The core of your addon

Valve has been nice enough to provide us with a nice addon template, you can find this file in the game/dota_addons/template_addon/scripts/vscripts directory. Just take this file as a basis for your addon, change the class name (CAddonTemplateGameMode) and start developing!

Extending the core

Having your core is nice and everything, but it does not do anything. So now we'll go over adding some basic functionality through the use of events (that happen ingame) and commands, which can be called from your UI

Events

Events are built into the engine and get called whenever something happens ingame. There are a lot of events such as dota_roshan_kill, dota_courier_lost or dota_player_gained_level. Most events have some extra data, for example on the dota_player_gained_level event, you can also find the playerID and level.
A list of events can be found here. You can also set up your own events in scripts/custom_events.txt (useful for UI interaction)

So how to use these events? There are two main components to using an event in your script, which are the listener and the handler.

The listener just tells your script to listen to a certain event, and once it occurs to execute a certain handler, which is just a function. You can set up a listener everywhere, usually it's done in the init function. A listener looks like this:

--add a listener to listen to event "event", that fires "Handler"
ListenToGameEvent( "event", Dynamic_Wrap( CustomGameMode, "Handler" ), self )

Example
Let's say we want to give 1000 gold to players that reach level 6. We add our listener to our InitGameMode() like so:
--This function is still the same as we already had, we just add one line
function CustomGameMode:InitGameMode()
    
    print( "Initialising mode!" )
	
    --We add the listener here, its handler is the function OnLevelUp
    ListenToGameEvent( "dota_player_gained_level", Dynamic_Wrap( CustomGameMode, "OnLevelUp" ), self )
        
    --start thinking
	GameRules:GetGameModeEntity():SetThink( "OnThink", self, "Think", 0.25 )
    
end
Now we add the handler for this event as a new function in our CustomGameMode object, for this example it looks like this:
function CustomGameMode:OnLevelUp( keys )
    
    print( "Somebody leveled up!" )
	
    --We want to give gold if a player reaches level 6, so we check his level
    local level = PlayerResource:GetLevel( keys.PlayerID )
    
    --Alternatively we can also just do 'if keys.level == 6 then'
    if level == 6 then
        --the player is level 6, so give him 1000 gold on top of what he already has
        PlayerResource:SetGold( keys.PlayerID, PlayerResource:GetGold( keys.PlayerID ) + 1000, true)
    end    
end
If you want more examples, you can look at the open source addons on the main page.

Commands

Commands are similar to events, due to the fact that they are also triggers for certain functions. The difference is that the game engine does not call any commands, you have to be the one to call them. This is the best way to have interaction between your flash UI and lua scripts.

We register a command like this:

Convars:RegisterCommand( "Command1", function(name, parameter)
    --Get the player that triggered the command
    local cmdPlayer = Convars:GetCommandClient()
	
    --If the player is valid: call our handler
    if cmdPlayer then 
        return self:Handler( cmdPlayer, parameter ) 
    end
 end, "A small description", 0 )
Now when the server recieves Command1 X in its console, our Handler function is called with parameter X (NB: the parameter is a string!).

Example
Let's say we have a button in our UI that allows the player to get ability points. From our UI we call the GiveAbilityPoints command with a parameter of how many points we want to give. For example a command call could look like this: 'GiveAbilityPoints 3', giving 3 points. We register it like this (place this in init or a function called from it):
Convars:RegisterCommand( "GiveAbilityPoints", function(name, parameter)
    --Get the player that triggered the command
    local cmdPlayer = Convars:GetCommandClient()
	
    --If the player is valid: call our handler
    if cmdPlayer then 
        return self:GivePlayerAbilityPoints( cmdPlayer, parameter ) 
    end
 end, "Gives a player some ability points", 0 )
Ofcourse we should also add the handler:
function CustomGameMode:GivePlayerAbilityPonits( player, numPoints )
    
    print( "Giving ability points" )
	
    --first we need to find the player's hero
    local hero = player:GetAssignedHero()
	
    --now give the hero the points, remember: numPoints is a string!
    hero:SetAbilityPoints( tonumber(numPoints) )
    
end

Tips and tricks

Printing a table in console

In case you would like to inspect all properties of a table you can use the DeepPrintTable( table ) function provided by valve.

Getting an item from inventory

Some functions require an item as input, but you can not get items by name from a player's inventory. If you need to do that, use this.
--a function that finds an item on a unit by name
function findItemOnUnit( unit, itemname, searchStash )
    --check if the unit has the item at all
    if not unit:HasItemInInventory( itemname ) then
        return nil
    end
    
    --set a search range depending on if we want to search the stash or not
    local lastSlot = 5
    if searchStash then
        lastSlot = 11
    end
    
    --go past all slots to see if the item is there
    for slot= 0, lastSlot, 1 do
        local item = unit:GetItemInSlot( slot )
        if item:GetAbilityName() == itemname then
            return item
        end
    end
    
    --if the item is not found, return nil (happens if the item is in stash and you are not looking in stash)
    return nil
end