[Гайд] Dota 2 Lua скриптинг

-ExotiC-

Какой-то ноунэйм
Команда форума
11 Авг 2014
498
56
customgames.ru
Цель данного урока - дать введение в систему скриптов используемую в Dota 2, и немного советов и приемов в их разработке. Предполагается что вы знакомы с базовыми понятиями ООП и Lua. Так же желательно использовать Notepad++ или Sublime Text






Основы



Мы будем работать в папке ваш_аддон/scripts/vscripts. Когда ваш аддон загружен движком Dota 2 выполняются два файла addon_init.lua и после этого addon_game_mode.lua. Согласно правилам эти файлы не ваши главные файлы, а просто файлы, которые необходимы. Будем считать что мы имеем объекты нашего проекта в addon_main.lua - это и есть главный файл.

addon_init.lua
Этот файл выполняется первым и как правило глобальные функции вызываются в нем. Допустим мы имеем два дополнительных файла в нашем аддоне: addon_main.lua (который содержит описания объектов в вашем аддоне) и util.lua (содержащий некоторые утилиты - маленькие полезные функции).

В вашем случае ваш addon_init.lua будет выглядеть примерно так:

Код:
-- Этот кусок кода заставляет перезагрузить модули, когда перезагружается скрипт.
if g_reloadState == nil then
  g_reloadState = {}
  for k,v in pairs( package.loaded ) do
    g_reloadState[k] = v
  end
else
  for k,v in pairs( package.loaded ) do
    if g_reloadState[k] == nil then
      package.loaded[k] = nil
    end
  end
end

-- Подключаем util.lua
require( "util" )
-- Подключаем addon_main.lua
require( "addon_main" )
-- Вы можете подключать любые .lua скрипты, которые вы хотите!


addon_game_mode.lua
Этот файл выполняется после addon_init.lua и отвечает за инициализацию аддона. Вот так выглядит пример:

Код:
--Делаем экземпляр нашего игрового объекта
local gameMode = CustomGameMode:new()
-- Вызываем функцию инициализации игрового мода
gameMode:InitGameMode()






Ядро вашего дополнения



Давайте взглянем на addon_main.lua - это ядро вашего мода. Если мы оставим главный код, то увидим это:

Код:
--[[
Custom game mode
]]
-------------------------------------------------------------------------------
-- Объявление класса
-------------------------------------------------------------------------------
if CustomGameMode == nil then
  CustomGameMode = {}
  CustomGameMode.__index = CustomGameMode
end

-- Конструктор
function CustomGameMode:new (o)
  o = o or {}
  setmetatable(o, self)
  HANDLE = o
  return o
end

-- Используется при вызове скрипта от способностей
function GetCustomGameMode()
  return HANDLE
end

--------------------------------------------------------------------------------
-- Глобальные переменные
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
-- Инициализируем CustGameMode
--------------------------------------------------------------------------------

function CustomGameMode:InitGameMode()
  
  print( "Initialising mode!" )
    
  -- Начинаем мышление
  local gameBase = Entities:FindAllByClassname('dota_base_game_mode')[1]
	gameBase:SetThink('Think', 'CustomGameMode', 0.25, self)
  
end

-------------------------------------------------------------------------------
-- Think (мыслительная) функция
-------------------------------------------------------------------------------
function CustomGameMode:Think()
  -- Возвращаем значение как долго мы ходим ждать для начала другого Think()
	return 0.25
end






Расширенное ядро



Добавим некоторые базовые функции с помощью событий (Events) и команды, которые могут быть вызваны из пользовательского интерфейса.

События

События строятся в игровом движке и могут быть вызваны, когда в игре произошло что-либо. Есть много событий, например такие как dota_roshan_kill, dota_courier_lost и dota_player_gained_level. Большинство событий имеют некоторые дополнительные атрибуты, например в событии dota_player_gained_level есть PlayerID и уровень. Вы также можете настроить свои собственные события в scripts/custom_events.txt.

Так как же использовать эти события? Есть два основных компонента для использования скриптов в вашем моде - listener (слушатель) и handler (обработчик).
Listener задает событие и как только это происходит, сразу же выполняется функция, заданная в свойствах Listener. Вы можете задать слушателя везде, но как правило, это делается в функции инициализации. Кусочек настроек слушателя:

Код:
-- Добавляем слушателя, для прослушивания события "event", и после события запускаем функцию "Handler"
ListenToGameEvent( "event", Dynamic_Wrap( CustomGameMode, "Handler" ), self )

Пример:
Допустим, мы хотим дать 1000 голды героям, которые достигают 6 уровня. Добавим нашего Слушателя в InitGameMode () следующим образом:

Код:
function CustomGameMode:InitGameMode()
  
  print( "Initialising mode!" )
	
  -- Мы добавим Слушателя здесь, его обработчик - функция OnLevelUp
  ListenToGameEvent( "dota_player_gained_level", Dynamic_Wrap( CustomGameMode, "OnLevelUp" ), self )
    
  -- Запускаем мыслителя
  local gameBase = thinkHack( "CustomGameMode", Dynamic_Wrap( CustomGameMode, "Think" ), 0.25, self)
  
end

Теперь мы добавляем обработчик для этого события в качестве новой функции в нашем CustomGameMode объекте, в данном примере это выглядит так:

Код:
function CustomGameMode:OnLevelUp( keys )
  
  print( "Somebody leveled up!" )
	
  -- Мы хотим дать золото, если игрок достиг 6 уровня, мы проверяем условие
  local level = PlayerResource:GetLevel( keys.PlayerID )
  
  if level == 6 then
  -- Если полученный уровень равен 6, то изменяем золото на +1000 (т.е. добавляем)
    PlayerResource:SetGold( keys.PlayerID, PlayerResource:GetGold( keys.PlayerID ) + 1000, true)
  end  
end


Команды

Команды похожи на события - они также вызываются для выполнения определенных функций. Разница в том, что игровой движок не вызывает никаких команд, только вы можете вызывать их. Это лучший способ для взаимодействия вашего пользовательского интерфейса (Flash UI) и Lua скриптов.

Мы регистрируем примерно такую команду

Код:
Convars:RegisterCommand( "Command1", function(name, parameter)
  -- Дать игроку, который вызвал команду
  local cmdPlayer = Convars:GetCommandClient()
	
  -- Если игрок существует: вызываем обработчик
  if cmdPlayer then 
    return self:Handler( cmdPlayer, parameter )
  end
 end, "A small description", 0 )

Теперь, когда сервер получит Command1 X в своей консоли, наша функция Обработчик вызывается с параметром X (параметр является строчкой - string).

Пример:

Предположим у нас есть кнопка в нашем UI, которая позволяет игроку получить очки способности. Из нашего UI мы вызываем команду GiveAbilityPoints с показателем того, сколько очков мы хотим дать. Например вызов команды может выглядеть следующим образом: "GiveAbilityPoints 3", дающая 3 очка. Регистрируем это примерно так:


Код:
Convars:RegisterCommand( "GiveAbilityPoints", function(name, parameter)
   -- Дать игроку, который вызвал команду
  local cmdPlayer = Convars:GetCommandClient()
	
  -- Если игрок существует: вызываем обработчик
  if cmdPlayer then 
    return self:GivePlayerAbilityPoints( cmdPlayer, parameter ) 
  end
 end, "Gives a player some ability points", 0 )

Конечно, мы должны также добавить обработчик:

Код:
function CustomGameMode:GivePlayerAbilityPonits( player, numPoints )
  
  print( "Giving ability points" )
	
  -- Сперва ищем героя
  local hero = player:GetAssignedHero()
	
  -- Теперь даем герою очки для прокачки, помните: NumPoints является строкой!
  hero:SetAbilityPoints( tonumber(numPoints) )
  
end






Советы и подсказки




Печатаем таблицу в консоль

Вы не можете увидеть все значения в таблице. Вы можете вывести её в консоль с помощью этой функции. Просто вызовите PrintTable( table ).

Код:
function PrintTable(t, indent, done)
  --print ( string.format ('PrintTable type %s', type(keys)) )
  if type(t) ~= "table" then return end

  done = done or {}
  done[t] = true
  indent = indent or 0

  local l = {}
  for k, v in pairs(t) do
    table.insert(l, k)
  end

  table.sort(l)
  for k, v in ipairs(l) do
    -- Игнорируем FDesc
    if v ~= 'FDesc' then
      local value = t[v]

      if type(value) == "table" and not done[value] then
        done [value] = true
        print(string.rep ("\t", indent)..tostring(v)..":")
        PrintTable (value, indent + 2, done)
      elseif type(value) == "userdata" and not done[value] then
        done [value] = true
        print(string.rep ("\t", indent)..tostring(v)..": "..tostring(value))
        PrintTable ((getmetatable(value) and getmetatable(value).__index) or getmetatable(value), indent + 2, done)
      else
        if t.FDesc and t.FDesc[v] then
          print(string.rep ("\t", indent)..tostring(t.FDesc[v]))
        else
          print(string.rep ("\t", indent)..tostring(v)..": "..tostring(value))
        end
      end
    end
  end
end


Получение предмета из инвентаря

Некоторые функции требуют предметы в качестве входных данных, но вы не можете получить имена предметов из инвентаря игрока. Если вам нужно сделать это, используйте этот пример:


Код:
  -- Функция, которая находит предмет на юните используя имя
function findItemOnUnit( unit, itemname, searchStash )
  -- Проверяем имеет ли юнит предметы
  if not unit:HashItemInInventory( itemname ) then
    return nil
  end
  
  -- Ставим диапазон поиска в зависимости от того, хотим ли мы чтобы искал в стэше (в тайнике)
  local lastSlot = 5
  if searchStash then
    lastSlot = 11
  end
  
  -- Проходим все слоты, чтобы проверить что предмет существует
  for slot= 0, lastSlot, 1 do
    local item = unit:GetItemInSlot( slot )
    if item:GetAbilityName() == itemname then
      return item
    end
  end
  
  -- Если этот предмет не найден, то возвращаем 0 - nil (происходит если предмет находится в стэша, а вы не ищете в нем)
  return nil
end





Перевод: http://removie.ru/d2wt
Оригинал на английском: http://yrrep.me/dota/dota-lua.html
 
Последнее редактирование:

Alexmork

Новичок
24 Авг 2014
1
0
есть скрипт, который ограничивает количество фрагов на арене, как его запустить в своем аддоне
Код:
MAX_KILLS = 50
function arena_nes:OnEntityKilled( keys )
local killedUnit = EntIndexToHScript( keys.entindex_killed )
local killerEntity = nil
if keys.entindex_attacker == nil then
return
end

killerEntity = EntIndexToHScript( keys.entindex_attacker )
killerEntity:SetGold(0, false)
killedEntity:SetGold(0, false)

local killedTeam = killedUnit:GetTeam()
local killerTeam = killerEntity:GetTeam()

if killedUnit:IsRealHero() == true then
local death_count_down = 5
killedUnit:SetTimeUntilRespawn(death_count_down)

if killedTeam == DOTA_TEAM_BADGUYS then
if killerTeam == 2 then
self.scoreRadiant = self.scoreRadiant + 1
end
elseif killedTeam == DOTA_TEAM_GOODGUYS then
if killerTeam == 3 then
self.scoreDire = self.scoreDire + 1
end
end

GameMode:SetTopBarTeamValue ( DOTA_TEAM_BADGUYS, self.scoreDire)
GameMode:SetTopBarTeamValue ( DOTA_TEAM_GOODGUYS, self.scoreRadiant )

if self.scoreDire >= MAX_KILLS then
GameRules:SetGameWinner(DOTA_TEAM_BADGUYS)
GameRules:MakeTeamLose(DOTA_TEAM_GOODGUYS)
GameRules:Defeated()
end
if self.scoreRadiant >= MAX_KILLS then
GameRules:SetGameWinner(DOTA_TEAM_GOODGUYS)
GameRules:MakeTeamLose(DOTA_TEAM_BADGUYS)
GameRules:Defeated()
end
end
end

__________________________
Длинный код прячь под спойлер
-ExotiC-
 
Последнее редактирование модератором:

Findes

Пользователь
6 Ноя 2018
20
0
Проект
Dota Strike
attempt to call method 'HashItemInInventory' (a nil value) Из за чего это ошибка? как я понимаю нужно указывать название предмета в кавычках?

Вот код
Код:
local hero_table = HeroList:GetAllHeroes()       
    for _, hero in pairs(hero_table) do
    if not hero:HashItemInInventory("item_bomb_c") then
        return nil
    else
        hero:RemoveItem("item_bomb_c")
    end
end
 

stranger568

Активный
7 Сен 2015
113
28
bmemov.ru
Проект
Birzha Memov
attempt to call method 'HashItemInInventory' (a nil value) Из за чего это ошибка? как я понимаю нужно указывать название предмета в кавычках?

Вот код
Код:
local hero_table = HeroList:GetAllHeroes()      
    for _, hero in pairs(hero_table) do
    if not hero:HashItemInInventory("item_bomb_c") then
        return nil
    else
        hero:RemoveItem("item_bomb_c")
    end
end
hero:HashItemInInventory
я на твоем месте присмотрел бы
 

HappyFeedFriends

Друзья CG
14 Авг 2017
540
32
Проект
Battle Heroes Arena
Цель данного урока - дать введение в систему скриптов используемую в Dota 2, и немного советов и приемов в их разработке. Предполагается что вы знакомы с базовыми понятиями ООП и Lua. Так же желательно использовать Notepad++ или Sublime Text






Основы



Мы будем работать в папке ваш_аддон/scripts/vscripts. Когда ваш аддон загружен движком Dota 2 выполняются два файла addon_init.lua и после этого addon_game_mode.lua. Согласно правилам эти файлы не ваши главные файлы, а просто файлы, которые необходимы. Будем считать что мы имеем объекты нашего проекта в addon_main.lua - это и есть главный файл.

addon_init.lua
Этот файл выполняется первым и как правило глобальные функции вызываются в нем. Допустим мы имеем два дополнительных файла в нашем аддоне: addon_main.lua (который содержит описания объектов в вашем аддоне) и util.lua (содержащий некоторые утилиты - маленькие полезные функции).

В вашем случае ваш addon_init.lua будет выглядеть примерно так:

Код:
-- Этот кусок кода заставляет перезагрузить модули, когда перезагружается скрипт.
if g_reloadState == nil then
  g_reloadState = {}
  for k,v in pairs( package.loaded ) do
    g_reloadState[k] = v
  end
else
  for k,v in pairs( package.loaded ) do
    if g_reloadState[k] == nil then
      package.loaded[k] = nil
    end
  end
end

-- Подключаем util.lua
require( "util" )
-- Подключаем addon_main.lua
require( "addon_main" )
-- Вы можете подключать любые .lua скрипты, которые вы хотите!


addon_game_mode.lua
Этот файл выполняется после addon_init.lua и отвечает за инициализацию аддона. Вот так выглядит пример:

Код:
--Делаем экземпляр нашего игрового объекта
local gameMode = CustomGameMode:new()
-- Вызываем функцию инициализации игрового мода
gameMode:InitGameMode()






Ядро вашего дополнения



Давайте взглянем на addon_main.lua - это ядро вашего мода. Если мы оставим главный код, то увидим это:

Код:
--[[
Custom game mode
]]
-------------------------------------------------------------------------------
-- Объявление класса
-------------------------------------------------------------------------------
if CustomGameMode == nil then
  CustomGameMode = {}
  CustomGameMode.__index = CustomGameMode
end

-- Конструктор
function CustomGameMode:new (o)
  o = o or {}
  setmetatable(o, self)
  HANDLE = o
  return o
end

-- Используется при вызове скрипта от способностей
function GetCustomGameMode()
  return HANDLE
end

--------------------------------------------------------------------------------
-- Глобальные переменные
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
-- Инициализируем CustGameMode
--------------------------------------------------------------------------------

function CustomGameMode:InitGameMode()
 
  print( "Initialising mode!" )
   
  -- Начинаем мышление
  local gameBase = Entities:FindAllByClassname('dota_base_game_mode')[1]
    gameBase:SetThink('Think', 'CustomGameMode', 0.25, self)
 
end

-------------------------------------------------------------------------------
-- Think (мыслительная) функция
-------------------------------------------------------------------------------
function CustomGameMode:Think()
  -- Возвращаем значение как долго мы ходим ждать для начала другого Think()
    return 0.25
end






Расширенное ядро



Добавим некоторые базовые функции с помощью событий (Events) и команды, которые могут быть вызваны из пользовательского интерфейса.

События

События строятся в игровом движке и могут быть вызваны, когда в игре произошло что-либо. Есть много событий, например такие как dota_roshan_kill, dota_courier_lost и dota_player_gained_level. Большинство событий имеют некоторые дополнительные атрибуты, например в событии dota_player_gained_level есть PlayerID и уровень. Вы также можете настроить свои собственные события в scripts/custom_events.txt.

Так как же использовать эти события? Есть два основных компонента для использования скриптов в вашем моде - listener (слушатель) и handler (обработчик).
Listener задает событие и как только это происходит, сразу же выполняется функция, заданная в свойствах Listener. Вы можете задать слушателя везде, но как правило, это делается в функции инициализации. Кусочек настроек слушателя:

Код:
-- Добавляем слушателя, для прослушивания события "event", и после события запускаем функцию "Handler"
ListenToGameEvent( "event", Dynamic_Wrap( CustomGameMode, "Handler" ), self )

Пример:
Допустим, мы хотим дать 1000 голды героям, которые достигают 6 уровня. Добавим нашего Слушателя в InitGameMode () следующим образом:

Код:
function CustomGameMode:InitGameMode()
 
  print( "Initialising mode!" )
   
  -- Мы добавим Слушателя здесь, его обработчик - функция OnLevelUp
  ListenToGameEvent( "dota_player_gained_level", Dynamic_Wrap( CustomGameMode, "OnLevelUp" ), self )
   
  -- Запускаем мыслителя
  local gameBase = thinkHack( "CustomGameMode", Dynamic_Wrap( CustomGameMode, "Think" ), 0.25, self)
 
end

Теперь мы добавляем обработчик для этого события в качестве новой функции в нашем CustomGameMode объекте, в данном примере это выглядит так:

Код:
function CustomGameMode:OnLevelUp( keys )
 
  print( "Somebody leveled up!" )
   
  -- Мы хотим дать золото, если игрок достиг 6 уровня, мы проверяем условие
  local level = PlayerResource:GetLevel( keys.PlayerID )
 
  if level == 6 then
  -- Если полученный уровень равен 6, то изменяем золото на +1000 (т.е. добавляем)
    PlayerResource:SetGold( keys.PlayerID, PlayerResource:GetGold( keys.PlayerID ) + 1000, true)
  end 
end


Команды

Команды похожи на события - они также вызываются для выполнения определенных функций. Разница в том, что игровой движок не вызывает никаких команд, только вы можете вызывать их. Это лучший способ для взаимодействия вашего пользовательского интерфейса (Flash UI) и Lua скриптов.

Мы регистрируем примерно такую команду

Код:
Convars:RegisterCommand( "Command1", function(name, parameter)
  -- Дать игроку, который вызвал команду
  local cmdPlayer = Convars:GetCommandClient()
   
  -- Если игрок существует: вызываем обработчик
  if cmdPlayer then
    return self:Handler( cmdPlayer, parameter )
  end
end, "A small description", 0 )

Теперь, когда сервер получит Command1 X в своей консоли, наша функция Обработчик вызывается с параметром X (параметр является строчкой - string).

Пример:

Предположим у нас есть кнопка в нашем UI, которая позволяет игроку получить очки способности. Из нашего UI мы вызываем команду GiveAbilityPoints с показателем того, сколько очков мы хотим дать. Например вызов команды может выглядеть следующим образом: "GiveAbilityPoints 3", дающая 3 очка. Регистрируем это примерно так:


Код:
Convars:RegisterCommand( "GiveAbilityPoints", function(name, parameter)
   -- Дать игроку, который вызвал команду
  local cmdPlayer = Convars:GetCommandClient()
   
  -- Если игрок существует: вызываем обработчик
  if cmdPlayer then
    return self:GivePlayerAbilityPoints( cmdPlayer, parameter )
  end
end, "Gives a player some ability points", 0 )

Конечно, мы должны также добавить обработчик:

Код:
function CustomGameMode:GivePlayerAbilityPonits( player, numPoints )
 
  print( "Giving ability points" )
   
  -- Сперва ищем героя
  local hero = player:GetAssignedHero()
   
  -- Теперь даем герою очки для прокачки, помните: NumPoints является строкой!
  hero:SetAbilityPoints( tonumber(numPoints) )
 
end






Советы и подсказки




Печатаем таблицу в консоль

Вы не можете увидеть все значения в таблице. Вы можете вывести её в консоль с помощью этой функции. Просто вызовите PrintTable( table ).

Код:
function PrintTable(t, indent, done)
  --print ( string.format ('PrintTable type %s', type(keys)) )
  if type(t) ~= "table" then return end

  done = done or {}
  done[t] = true
  indent = indent or 0

  local l = {}
  for k, v in pairs(t) do
    table.insert(l, k)
  end

  table.sort(l)
  for k, v in ipairs(l) do
    -- Игнорируем FDesc
    if v ~= 'FDesc' then
      local value = t[v]

      if type(value) == "table" and not done[value] then
        done [value] = true
        print(string.rep ("\t", indent)..tostring(v)..":")
        PrintTable (value, indent + 2, done)
      elseif type(value) == "userdata" and not done[value] then
        done [value] = true
        print(string.rep ("\t", indent)..tostring(v)..": "..tostring(value))
        PrintTable ((getmetatable(value) and getmetatable(value).__index) or getmetatable(value), indent + 2, done)
      else
        if t.FDesc and t.FDesc[v] then
          print(string.rep ("\t", indent)..tostring(t.FDesc[v]))
        else
          print(string.rep ("\t", indent)..tostring(v)..": "..tostring(value))
        end
      end
    end
  end
end


Получение предмета из инвентаря

Некоторые функции требуют предметы в качестве входных данных, но вы не можете получить имена предметов из инвентаря игрока. Если вам нужно сделать это, используйте этот пример:


Код:
  -- Функция, которая находит предмет на юните используя имя
function findItemOnUnit( unit, itemname, searchStash )
  -- Проверяем имеет ли юнит предметы
  if not unit:HashItemInInventory( itemname ) then
    return nil
  end
 
  -- Ставим диапазон поиска в зависимости от того, хотим ли мы чтобы искал в стэше (в тайнике)
  local lastSlot = 5
  if searchStash then
    lastSlot = 11
  end
 
  -- Проходим все слоты, чтобы проверить что предмет существует
  for slot= 0, lastSlot, 1 do
    local item = unit:GetItemInSlot( slot )
    if item:GetAbilityName() == itemname then
      return item
    end
  end
 
  -- Если этот предмет не найден, то возвращаем 0 - nil (происходит если предмет находится в стэша, а вы не ищете в нем)
  return nil
end





Перевод: http://removie.ru/d2wt
Оригинал на английском: http://yrrep.me/dota/dota-lua.html













проверку на существование айтема забыл, будут ошибки
 
Реклама: