CustomGames.ru - Dota 2 пользовательские игры

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

0 Пользователей и 1 Гость просматривают эту тему.

Онлайн -ErøtiC-

  • Администратор
  • 335
  • Мощь: 3
  • Забанен
[Гайд] Dota 2 Lua скриптинг
« : 12-08-2014, 13:07:47 »
Цель данного урока - дать введение в систему скриптов используемую в 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





Перевод: removie.ru/d2wt
Оригинал на английском: yrrep.me/dota/dota-lua.html
« Последнее редактирование: 14-08-2014, 18:29:45 от -ExotiC- »

Оффлайн Alexmork

  • 1
  • Мощь: 0
Re: [Гайд] Dota 2 Lua скриптинг
« Ответ #1 : 24-08-2014, 16:43:20 »
есть скрипт, который ограничивает количество фрагов на арене, как его запустить в своем аддоне
Спойлер
Код
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-
« Последнее редактирование: 24-08-2014, 16:49:07 от -ExotiC- »

Онлайн -ErøtiC-

  • Администратор
  • 335
  • Мощь: 3
  • Забанен
Re: [Гайд] Dota 2 Lua скриптинг
« Ответ #2 : 24-08-2014, 16:51:18 »
Alexmork,
Делай по гайду: customgames.ru/20...0%B5%D0%B4%D1%8B/