Dota 2 Workshop Tools/Scripting/Listening to game events

From Valve Developer Community
Jump to: navigation, search
Русский

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).

Warning: Because Valve sometimes change the events, the list Built-In Engine Events is not always accurate. You can use the pairs function to see which keys a table has:

 for key,val in pairs(tbl) do
     print(key, val)
 end

(Replace tbl with the table you want to inspect.)

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
Tip:You can use the RollPercentage function to introduce a drop chance.

FAQ

Question: What does Dynamic_Wrap do?

Answer: Use Dynamic_Wrap if you want the console command "script_reload" to also reload your listeners.

Tip:The source code of Dynamic_Wrap can be found here: /path/to/Steam/SteamApps/common/dota 2 beta/game/core/scripts/vscripts/utils/vscriptinit.lua.

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.