Dota 2 Workshop Tools/Scripting/Listening to game events

This is a tutorial for beginners. If you see something that can be improved, please improve it!
== Introduction ==
This tutorial is about the API function ListenToGameEvent. If you call ListenToGameEvent with an event name and a function, the game will call that function every time the event happens.
=== Example 1 ===
Every time a player levels up, we show a message to all players:
 -- scripts/vscripts/addon_game_mode.lua
 function LevelUpMessage (eventInfo)
 Say(nil, "Someone just leveled up!", false)
 end
 function Activate ()
 ListenToGameEvent("dota_player_gained_level", LevelUpMessage, nil)
 end
We have two functions here:
* LevelUpMessage: Here we use the API function Say to show a message to all players.
* Activate: The game calls this function when it loads the addon. We want the game to call LevelUpMessage every time a player levels up. To do this we call the API function ListenToGameEvent with "dota_player_gained_level" and LevelUpMessage as arguments.
=== Example 2 ===
When a player reaches level 6, we show a message to all players:
 -- scripts/vscripts/addon_game_mode.lua
 function Level6Message (eventInfo)
 if eventInfo.level == 6 then
 Say(nil, "Someone just reached level 6", false)
 end
 end
 function Activate ()
 ListenToGameEvent("dota_player_gained_level", Level6Message, nil)
 end
Note: In Level6Message we use the argument, eventInfo, to get the level that the player has reached.
== The function ListenToGameEvent ==
Here is the signature of ListenToGameEvent:
 int ListenToGameEvent(string eventName, handle functionToCall, handle context)
=== Parameters ===
; eventName
: The name of the event that you want to listen to. See the page Built-In Engine Events for a complete list.
; functionToCall
: The function that you want the game to call when the event happens.
; context
: Here you can use either nil or an object (for example, a table or a function). If you use an object here, then it will be passed as the first argument to your function when the event happens.
== Event details ==
By calling ListenToGameEvent with an event name and a function, you tell the game to call that function every time the event happens. When the game calls the function, it passes a table as an argument to it. This table contains details of the event. On the page Built-In Engine Events you can see which keys the table has. For example, on this page you can see that the table for "dota_player_gained_level" has a key called level (cf. Example 2 above).
== Your listener function ==
If your function is defined like this:
 function MyFunction (arg)
 -- some code here
 end
then call ListenToGameEvent like this:
 ListenToGameEvent("some_event_name", MyFunction, nil)
But what if your function is defined like this?
 function MyClass:MyFunction (arg)
 -- some code here
 end
Remember: This is a short way of writing
 function MyClass.MyFunction (self, arg)
 -- some code here
 end
So your function actually takes two arguments. In this situation, call ListenToGameEvent like this:
 ListenToGameEvent("some_event_name", MyClass.MyFunction, ?)
or like this (see the FAQ below):
 ListenToGameEvent("some_event_name", Dynamic_Wrap(MyClass, "MyFunction"), ?)
Replace ? with the object that you want to use as self in your function. Here is an example:
=== Example 3 ===
When an NPC spawns, we print the total number of times this has happened:
 -- scripts/vscripts/addon_game_mode.lua
 if MyClass == nil then
 MyClass = class({})
 end
 function MyClass:InitGameMode ()
 self.numSpawned = 0
 ListenToGameEvent("npc_spawned", MyClass.MyFunction, self)
 end
 function MyClass:MyFunction ()
 self.numSpawned = self.numSpawned + 1
 print("Number of times an NPC has spawned: " .. self.numSpawned)
 end
 function Activate ()
 GameRules.MyAddon = MyClass()
 GameRules.MyAddon:InitGameMode()
 end
== More examples ==
=== Example 4 ===
When a player picks a hero, we give that hero a blink dagger:
 -- scripts/vscripts/addon_game_mode.lua
 function GiveBlinkDagger (hero)
 if hero:HasRoomForItem("item_blink", true, true) then
 local dagger = CreateItem("item_blink", hero, hero)
 dagger:SetPurchaseTime(0)
 hero:AddItem(dagger)
 end
 end
 function OnHeroPicked (event)
 local hero = EntIndexToHScript(event.heroindex)
 GiveBlinkDagger(hero)
 end
 function Activate ()
 ListenToGameEvent("dota_player_pick_hero", OnHeroPicked, nil)
 end
=== Example 5 ===
The team that first gets 5 hero kills wins:
 -- scripts/vscripts/addon_game_mode.lua
 if CustomGameMode == nil then
 CustomGameMode = class({})
 end
 function Activate ()
 GameRules.CustomAddon = CustomGameMode()
 GameRules.CustomAddon:InitGameMode(5)
 end
 function CustomGameMode:InitGameMode (killLimit)
 self.killLimit = killLimit
 ListenToGameEvent("entity_killed", Dynamic_Wrap(CustomGameMode, "OnEntityKilled"), self)
 end
 function CustomGameMode:OnEntityKilled ()
 if PlayerResource:GetTeamKills(DOTA_TEAM_GOODGUYS) == self.killLimit then
 GameRules:SetGameWinner(DOTA_TEAM_GOODGUYS)
 elseif PlayerResource:GetTeamKills(DOTA_TEAM_BADGUYS) == self.killLimit then
 GameRules:SetGameWinner(DOTA_TEAM_BADGUYS)
 end
 end
=== Example 6 ===
When a unit is killed, it drops a healing salve (item_flask):
 -- scripts/vscripts/addon_game_mode.lua
 function CreateDrop (itemName, pos)
 local newItem = CreateItem(itemName, nil, nil)
 newItem:SetPurchaseTime(0)
 CreateItemOnPositionSync(pos, newItem)
 newItem:LaunchLoot(false, 300, 0.75, pos + RandomVector(RandomFloat(50, 350)))
 end
 function OnEntityKilled (event)
 local killedEntity = EntIndexToHScript(event.entindex_killed)
 if killedEntity ~= nil then
 CreateDrop("item_flask", killedEntity:GetAbsOrigin())
 end
 end
 function Activate ()
 ListenToGameEvent("entity_killed", OnEntityKilled, nil)
 end
== FAQ ==
Question: What does Dynamic_Wrap do?
Answer: Use Dynamic_Wrap if you want the console command "script_reload" to also reload your listeners.
Here are some more details: Suppose you have registered a listener like this:
 ListenToGameEvent("some_event_name", MyClass.MyFunction, someObj)
If you change the source code of MyClass.MyFunction and then type "script_reload" in the console, your change will not be visible. It will still be the old version of MyClass.MyFunction that gets called when the event happens. To fix this, you can use Dynamic_Wrap:
 ListenToGameEvent("some_event_name", Dynamic_Wrap(MyClass, "MyFunction"), someObj)
Now, every time the event happens, the game will look up the field MyFunction on MyClass and then run the function this field points to. When you change the source code of MyClass.MyFunction and then type "script_reload" in the console, the MyFunction field will be updated. So when the event happens, the game will run the new version of the function.
Category:Dota 2 Workshop Tools