Добавлен , опубликован
От хозяина блога: статья не моя, я лишь решил её перекопировать на хгм, что бы местные могли с ней ознакомиться.
Автор перевода: 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. Вы можете задать слушателя везде, но как правило, это делается в функции инициализации. Кусочек настроек слушателя:
-- Добавляем слушателя, для прослушивания события "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
`
ОЖИДАНИЕ РЕКЛАМЫ...
2
25
10 лет назад
Отредактирован sleep
2
Если админы сюда заглянут, то просьба: добавить на сайте в систему распознания ЯП Lua язык, сейчас поставил на джасс.
Этот комментарий удален
4
34
10 лет назад
4
ок добавим.
4
24
10 лет назад
4
Не думал что когда-нибудь скажу это, но от доты2 таки есть польза.
Во-первых теперь каждый желающий "создать свою крутую карту для доты" рано или поздно будет сталкиваться с необходимостью владеть Lua на базовом уровне, а значит станет меньше высказываний вида "да ну его в Ж этот луа, давайте лучше на js - его я уже знаю".
Во-вторых на XGM может появиться подсветка синтаксиса Lua.
2
29
10 лет назад
2
Во-вторых на XGM может появиться подсветка синтаксиса Lua.
можно было и просто так попросить....
2
24
10 лет назад
2
alexprey, если мне не изменяет память, то какой-то не очень популярный язык уже просили добавить в список, может и Lua, на что получили ответ, что ради двух с половиной человек, которые будут этим пользоваться, добавлять новую схему подсветки никто не будет ибо лень. Правда это довольно давно было.
2
29
10 лет назад
2
prog, времена меняются
2
15
10 лет назад
2
Этого я и ждал!
2
5
9 лет назад
2
Я конечно не против публикации этого перевода, но с бэклинком на источник было бы лучше.
Чтобы оставить комментарий, пожалуйста, войдите на сайт.