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.
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
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!
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 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 )
--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 ) endNow 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 endIf you want more examples, you can look at the open source addons on the main page.
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!).
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
--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