От хозяина блога: статья не моя, я лишь решил её перекопировать на хгм, что бы местные могли с ней ознакомиться.
Автор перевода: ExotiC
Англоязычная версия статьи
Автор перевода: ExotiC
Англоязычная версия статьи
Цель данного урока – дать введение в систему скриптов используемую в 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. Вы можете задать слушателя везде, но как правило, это делается в функции инициализации. Кусочек настроек слушателя:
Listener задает событие и как только это происходит, сразу же выполняется функция, заданная в свойствах Listener. Вы можете задать слушателя везде, но как правило, это делается в функции инициализации. Кусочек настроек слушателя:
-- Добавляем слушателя, для прослушивания события "event", и после события запускаем функцию "Handler"
ListenToGameEvent( "event", Dynamic_Wrap( CustomGameMode, "Handler" ), self )
Пример:
Допустим, мы хотим дать 1000 голды героям, которые достигают 6 уровня. Добавим нашего Слушателя в InitGameMode () следующим образом:
Допустим, мы хотим дать 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 очка. Регистрируем это примерно так:
Предположим у нас есть кнопка в нашем 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
Ред. sleep
Во-первых теперь каждый желающий "создать свою крутую карту для доты" рано или поздно будет сталкиваться с необходимостью владеть Lua на базовом уровне, а значит станет меньше высказываний вида "да ну его в Ж этот луа, давайте лучше на js - его я уже знаю".
Во-вторых на XGM может появиться подсветка синтаксиса Lua.