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

Кодим функции: ульт акса (axe_culling_blade)

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

Оффлайн Илья

  • Супермодератор
  • 2131
  • Мощь: 21
Предисловие
В процессе разработки аддонов иногда приходится реализовывать стандартные умения героев с нуля вручную, чтобы изменить их функционал под себя. И последний раз когда я это делал, меня посетила мысль - выкладывать подобные реализации с разъяснением кода на форуме, дабы дать новичкам пищу для пережевывания, а старичкам, возможно, заготовки на будущее.


Для кого эта статья
Целевой аудиторией являются новички в моддинге, которые хотят освоить скриптинг на языке lua под workshop. Реализация стандартных абилок с нуля - это превосходный способ освоить механику создания своих собственных умений в workshope.

Что мы будем делать
В этой статье мы реализуем ульт акса. Он может немного отличаться от оригинала, но эти отличия вполне можно назвать мелочами, которыми мы позволим себе пренебречь. Кроме того, я пренебрегаю различными тонкостями, что могут влиять на отработку ульта в тех или иных ситуациях, поскольку целью статьи является не создание точной копии умения, а демонстрация процесса создания умения, его прототипа. Естественно, кто-то может найти возможные ошибки или более простые способы решения тех или иных проблем - все это можно изложить в комментариях к посту.

Culling Blade
Итак, что же мы видим, когда используем аксом его ульт:
"ну, он подпрыгивает и бьет наотмашь своим топором противника, лопая его тушку и разбрызгивая на всех вокруг его кишки и кровь"

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

А теперь рассмотрим это описание с точки зрения абилки workshopa.
Здесь мы имеем два варианта, когда все плохо:

1. Применение на цель (target), которая является героем (unit hero)
2. Проверка здоровья цели (попадает ли под порог убийства)
3. Нанесение урона цели
4. Проигрывание анимации


И когда все хорошо:
1. Применение на цель (target), которая является героем (unit hero)
2. Проверка здоровья цели (попадает ли под порог убийства)
3. Убийство цели
4. Поиск союзников в определенном радиусе от нашего героя
5. Наложение бафа (модификатора) на себя и союзников, попавших в радиус.
6. Проигрывание анимации


Естественно шагов можно еще больше накрутить, но мы придерживаемся политики "прозрачности" повествования и лени.

Что мы должны вынести из этого анализа? То, что мы имеем два объекта - абилку и модификатор (баф). Модификатор я реализовывать в ручную не стал, так как нашел здесь подходящий мне modifier_axe_culling_blade_boost. Поэтому сразу переходим к реализации абилки:

Первое, что нам надо, это подготовить скрипт-файл, в котором будем описывать механику работы абилки. Обычно создают подобные файлы по адресу:

 ...\Steam\SteamApps\common\dota 2 beta\game\dota_addons\"имя аддона"\scripts\vscripts\abilities

Можете и не создавать папку abilities или создать папку с иным именем, это не важно, главное, чтобы лежало все в папке vscripts.  Создаем пустой файл с именем axe_culling_blade_custom.lua (ну, как все делают - создаешь txt файл, а там меняешь его формат с txt на lua обычным переименованием) .

Далее заходим в папку

 ...\Steam\SteamApps\common\dota 2 beta\game\dota_addons\"имя аддона"\scripts\npc\

и открываем файл npc_abilities_custom.txt.

В нем создаем обложку абилки. И, так как я не хотел придумывать чего-то нового (параметры), то просто копируем оригинал и меняем его под себя:

жмяк
Код
"axe_culling_blade_custom"
{
// General
//--------------------------------------------------------------------------------------------------------

"BaseClass" "ability_lua"
"AbilityTextureName" "axe_culling_blade"
"ScriptFile" "abilities/axe_culling_blade_custom.lua"
"AbilityType" "DOTA_ABILITY_TYPE_ULTIMATE
"AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET"
"SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO"
"AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY"
"AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO"
"AbilityUnitTargetFlags" "DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES"
"SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES"
"AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL"
"FightRecapLevel" "2"

// Casting
//--------------------------------------------------------------------------------------------------------
"AbilityCastRange" "150"
"AbilityCastPoint" "0.3"

// Time
//--------------------------------------------------------------------------------------------------------
"AbilityCooldown" "15 10 5"

// Cost
//--------------------------------------------------------------------------------------------------------
"AbilityManaCost" "10"

"AbilitySpecial"
{
"01"
{
"var_type" "FIELD_INTEGER"
"kill_threshold" "250 325 400"
}
"02"
{
"var_type" "FIELD_INTEGER"
"damage" "150 250 300"
}
"03"
{
"var_type" "FIELD_FLOAT"
"speed_duration" "3"
}
"04"
{
"var_type" "FIELD_INTEGER"
"speed_aoe" "900"
}
"05"
{
"var_type" "FIELD_INTEGER"
"kill_threshold_scepter" "300 500 700"
}
"06"
{
"var_type" "FIELD_FLOAT"
"speed_duration_scepter" "10"
}
}

}
[свернуть]



Что мы здесь указали?

BaseClass - каков наш объект, тип реализации умения, в нашем случае это реализация через lua скрипт - ability_lua
ScriptFile - путь к самому скрипту, то есть -   abilities/axe_culling_blade_custom.lua
AbilityType - тип абилки указываем ультой (хотя это все можно прописать в скрипте) - DOTA_ABILITY_TYPE_ULTIMATE      
AbilityBehavior - указываем, что у нас абилка будет кастоваться на цель - DOTA_ABILITY_BEHAVIOR_UNIT_TARGET
AbilityUnitTargetTeam - цель эта является врагом - DOTA_UNIT_TARGET_TEAM_ENEMY
AbilityUnitTargetType - и цель эта должна быть героем - DOTA_UNIT_TARGET_HERO

Остальное все расписывать не буду, все это можно найти на форуме.


Теперь перейдем в файл скрипта axe_culling_blade_custom.lua:

тык
Код
axe_culling_blade_custom = class({})

function axe_culling_blade_custom:GetCastAnimation() 
    return ACT_DOTA_CAST_ABILITY_4 
end

 function axe_culling_blade_custom:GetBehavior()
     return DOTA_ABILITY_BEHAVIOR_UNIT_TARGET
 end


function axe_culling_blade_custom:OnSpellStart()

    local caster = self:GetCaster()
    local target = self:GetCursorTarget()
    local killThreshold = self:GetSpecialValueFor("kill_threshold_scepter")
    local speedDuration = self:GetSpecialValueFor("speed_duration")

    if caster:HasItemInInventory("item_ultimate_scepter") then
        killThreshold = self:GetSpecialValueFor("kill_threshold_scepter")
        speedDuration = self:GetSpecialValueFor("speed_duration_scepter")
    end
   
    if target:GetHealth() <= killThreshold then
        EmitSoundOn("Hero_Axe.Culling_Blade_Success", self:GetCaster())
        self:KillUnit(target)
        self:GiveBaff(caster,speedDuration)     
    else
        EmitSoundOn("Hero_Axe.Culling_Blade_Fail", self:GetCaster())
        ApplyDamage( { victim = target, attacker = caster, damage = self:GetSpecialValueFor("damage"),
                        damage_type = DAMAGE_TYPE_PURE, ability = self} )
    end
end


function axe_culling_blade_custom:KillUnit(target)
    target:ForceKill(false)
    ParticleManager:CreateParticle("particles/units/heroes/hero_life_stealer/life_stealer_infest_emerge_bloody.vpcf", PATTACH_ABSORIGIN, target)
end


function axe_culling_blade_custom:GiveBaff(caster,speedDuration)
    local units = FindUnitsInRadius( caster:GetTeamNumber(), caster:GetAbsOrigin(), caster, self:GetSpecialValueFor("speed_aoe") ,
        DOTA_UNIT_TARGET_TEAM_FRIENDLY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, 0, false )
       
    for i = 1, #units do       
        if units[ i ] then
            units[ i ]:AddNewModifier(caster, self, "modifier_axe_culling_blade_boost", { duration = speedDuration}) 
            ParticleManager:CreateParticle("particles/units/heroes/hero_ogre_magi/ogre_magi_bloodlust_buff_symbol.vpcf", PATTACH_ABSORIGIN, units[ i ])


        end
    end 
end

function axe_culling_blade_custom:IsStealable()
    return true
end

function axe_culling_blade_custom:IsHiddenWhenStolen()
return true
end
[свернуть]

Ну и давайте по кусочкам пережевывать:

Код
axe_culling_blade_custom = class({})
- говорим, что переменная с именем axe_culling_blade_custom теперь является объектом типа класс class({}). Без этого никуда, это самая важная строчка, сердце скрипта и нашей абилки.

Функция GetCastAnimation() - к ней обращается компилятор, когда работает с анимацией каста абилки.
Код
function axe_culling_blade_custom:GetCastAnimation()  
    return ACT_DOTA_CAST_ABILITY_4 
end

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

Далее мы так же как и в случае с анимацией, переопределяем метод GetBehavior():

Код
 function axe_culling_blade_custom:GetBehavior() 
     return DOTA_ABILITY_BEHAVIOR_UNIT_TARGET
 end

Иными словами, говорим, чтобы эта функция на любые расспросы компилятором нашего файла о типе абилки возвращала глобалку DOTA_ABILITY_BEHAVIOR_UNIT_TARGET.

Теперь механизм, логика поведения нашей абилки, переопределяем метод OnSpellStar(), но сначала пара уточнений:
self - текущий класс, т.е. наша абилка
local - тип переменной, что мы определяем (то есть локальный тип переменной, существующей в пределах функции/ блока)

Код
function axe_culling_blade_custom:OnSpellStart()

    local caster = self:GetCaster() // заводим под кастера (тот, кто активирует абилку) переменную caster
    local target = self:GetCursorTarget() //заводим под цель (кого атакуем) переменную target
    local killThreshold = self:GetSpecialValueFor("kill_threshold_scepter") //считываем порог убийства из special переменных нашей абилки в npc_abilities_custom.txt и заносим в переменную killThreshold
    local speedDuration = self:GetSpecialValueFor("speed_duration")//считываем продолжительность бафа из special переменных и заносим значение в переменную speedDuration

//здесь проверяем наличие в инвентаре игрока аганима, чтобы усилить те или иные параметры
    if caster:HasItemInInventory("item_ultimate_scepter") then
        killThreshold = self:GetSpecialValueFor("kill_threshold_scepter")
        speedDuration = self:GetSpecialValueFor("speed_duration_scepter")
    end
 
//теперь проверка здоровья цели   
    if target:GetHealth() <= killThreshold then //вариант, когда все клево
        EmitSoundOn("Hero_Axe.Culling_Blade_Success", self:GetCaster()) //отыгрываем звук успеха
        self:KillUnit(target) //самодельная функция убийства, реализация будет ниже
        self:GiveBaff(caster,speedDuration)  //даем баф (самодельная функция, её реализация будет ниже)   
    else //вариант, когда все плохо
        EmitSoundOn("Hero_Axe.Culling_Blade_Fail", self:GetCaster()) //отыгрываем унылый звук
        ApplyDamage( { victim = target, attacker = caster, damage = self:GetSpecialValueFor("damage"),
                        damage_type = DAMAGE_TYPE_MAGICAL, ability = self} ) //наносим урон цели, магический
    end
end


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

Код
function axe_culling_blade_custom:KillUnit(target)  //в функцию передается цель, target, с ней и работаем
    target:ForceKill(false) //принудительно убиваем цель
    ParticleManager:CreateParticle("particles/units/heroes/hero_life_stealer/life_stealer_infest_emerge_bloody.vpcf", PATTACH_ABSORIGIN, target) //отрисовываем кровищу (оригинальный партикль ульты акса у меня не работал, взял гули)
end

Код
function axe_culling_blade_custom:GiveBaff(caster,speedDuration) //в функцию передается кастующий и время бафа
   //ищем героев союзных в радиусе speed_aoe, что в txt файле в специалках указывали
 local units = FindUnitsInRadius( caster:GetTeamNumber(), caster:GetAbsOrigin(), caster, self:GetSpecialValueFor("speed_aoe") ,
        DOTA_UNIT_TARGET_TEAM_FRIENDLY, DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, 0, false )
       
    for i = 1, #units do //цикл от 1 до количества найденных юнитов       
        if units[ i ] then //если юнит существует, т.е. реальный объект, а не nil
            //вешаем модификатор
            units[ i ]:AddNewModifier(caster, self, "modifier_axe_culling_blade_boost", { duration = speedDuration})
           //отрисовываем какую-нибудь анимацию
             ParticleManager:CreateParticle("particles/units/heroes/hero_ogre_magi/ogre_magi_bloodlust_buff_symbol.vpcf", PATTACH_ABSORIGIN, units[ i ])
        end
    end 
end

Теперь вспоминаем о многоуважаемом Рубике и переопределяем два метода, существующих только из-за его ульты:

//хотим ли мы, чтобы наш ульт можно было спиздить? Ну, я не вижу проблем в его отработке на другом герое - таймерами мы никакими не пользовались, так что ульт независим. Пускай его пиздят

Код
function axe_culling_blade_custom:IsStealable()
    return true
end

//хрен знает, что это за функция - скрыт, когда украден - я не вникал, но если её не переопределить, то могут быть ошибки в случае, когда в функции выше стоит true

Код
function axe_culling_blade_custom:IsHiddenWhenStolen()
return true
end

Поздравляю, вы превосходны, абилка готова.


Послесловие
Устал. Вспомнил, почему не люблю подобные статьи писать. Думал более дотошно все рассказать, с картинками, но получилось как есть. Вполне приемлемо и некоторые моменты для новичков я уточню здесь:

Много, много мелочей  и тонкостей, о которых я не упоминал и не реализовывал в коде, но, как уже говорилось, мы преследуем цель "прозрачности" и "рабочего прототипа", а не "идеального повторения".

Все "переопределяемые" функции можно найти здесь в блоке CDOTABaseAbility. В смысле, те, что можно и иногда нужно переопределять, когда создаете свои абилки.

Все константы, т.е. анимация атаки, типы юнитов, что используются в функциях, сами функци и все, что я  не объяснял так же находится там и имеет свое (фиговое, но со временем понятное) описание.

Звуки, где взять их названия? Распаковываете папку soundevents из архива фалов дотки (на форуме поищите инфу, как это делается, ключевое слово GCFScape). И далее в папочке game_sounds_heroes открываете файл нужного вам героя обычным текстовым редактором и смотрите. А в папочке sounds, что лежит там же, где и soundevents, можно и все остальные звуки найти, не только героев.

Партикли, анимация, где её искать? Ну, запускаете в workshope  утилиту Particle Manager, или в hammere во вкладке particle. На понравившемся клацаете правой и читаете Assert Info - и вот вам путь до партикля. Так же советую распаковать себе папочку  particles из архива дотовксих файлов, чтобы там спокойно смотреть путь к той или иной папке партиклей (для подгрузки).


Советую в дальнейшем самостоятельно реализовывать абилки в такой последовательности
* пассивки - самые простые в реализации умения
* направленные target (как наш ульт акса)
* ненаправленные no target (ну там всякие включаемые или, например,  агр у акса)
* направленные point (ну, на точку)
* направленные AOE (по сути те же, что и предыдущие, но с областью)

Естественно это мой взгляд и наверняка я какие-нибудь забыл, но к тому времени как освоите хотя бы вплоть до "no target", уже будете "шарить".

Дополнять/преобразовывать статью я вряд ли буду (скорее что-то новое выложу), но все может быть.
Если хотите почитать похожий от меня материал,  то вбивайте в поиск по форуму "кодим функции" и читайте.
« Последнее редактирование: 04-07-2016, 16:05:19 от Илья »

Оффлайн CryDeS

  • Друзья CG
  • 1212
  • Мощь: 12
>Объектом типа класс
Объект это экземпляр класса же, он не может быть типом класса, ну. А так отлично.

Оффлайн Илья

  • Супермодератор
  • 2131
  • Мощь: 21
Верное замечание  ;)

Верное в том случае, если здесь действительно объявление экземпляра такого класса, как абилка. В противном случае, мы создаем объект, которому назначаем "тип данных" класс.
« Последнее редактирование: 04-07-2016, 18:28:14 от Илья »

Оффлайн CryDeS

  • Друзья CG
  • 1212
  • Мощь: 12
Верное замечание  ;)

Верное в том случае, если здесь действительно объявление экземпляра такого класса, как абилка. В противном случае, мы создаем объект, которому назначаем "тип данных" класс.
Код
axe_culling_blade_custom = class({})
Неа, мы создаем экземпляр класса(объект) типа таблица ({}).
class(data_type) - функция создающая из данного типа объект.

Оффлайн Илья

  • Супермодератор
  • 2131
  • Мощь: 21
Ну да, верно, в lua же все через таблицы реализовано.

Оффлайн qofma

  • 81
  • Мощь: 0
Верно ли я понял,улучшить скил можно любым предметом ?
Моя кастомная карта:Shadow Fiend Wars

Оффлайн Илья

  • Супермодератор
  • 2131
  • Мощь: 21
В смысле?

Если ты про этот блок

Код
    if caster:HasItemInInventory("item_ultimate_scepter") then
        killThreshold = self:GetSpecialValueFor("kill_threshold_scepter")
        speedDuration = self:GetSpecialValueFor("speed_duration_scepter")
    end

Ну, да, можешь вместо аганима прописать другую вещь и будет тебе улучшение скилла.

Оффлайн CryDeS

  • Друзья CG
  • 1212
  • Мощь: 12
В смысле?

Если ты про этот блок

Код
    if caster:HasItemInInventory("item_ultimate_scepter") then
        killThreshold = self:GetSpecialValueFor("kill_threshold_scepter")
        speedDuration = self:GetSpecialValueFor("speed_duration_scepter")
    end

Ну, да, можешь вместо аганима прописать другую вещь и будет тебе улучшение скилла.
Немного не правильно делаешь, если на герое висит съеденный аганим от алхима?
Используй caster:HasScepter()

Оффлайн Илья

  • Супермодератор
  • 2131
  • Мощь: 21
Ну да, для аганимов от алхима нужно делать так.

Оффлайн CryDeS

  • Друзья CG
  • 1212
  • Мощь: 12
Ну да, для аганимов от алхима нужно делать так.
А так же мб кастомный айтем с параметром MODIFIER_PROPERTY_IS_SCEPTER

Оффлайн somsim

  • 23
  • Мощь: 0
  • Solar_Max_2
Re: Кодим функции: ульт акса (axe_culling_blade)
« Ответ #10 : 12-05-2017, 16:52:27 »
Предисловие
В процессе разработки аддонов иногда приходится реализовывать стандартные умения героев с нуля вручную, чтобы изменить их функционал под себя. И последний раз когда я это делал, меня посетила мысль - выкладывать подобные реализации с разъяснением кода на форуме, дабы дать новичкам пищу для пережевывания, а старичкам, возможно, заготовки на будущее.


Для кого эта статья
Целевой аудиторией являются новички в моддинге, которые хотят освоить скриптинг на языке lua под workshop. Реализация стандартных абилок с нуля - это превосходный способ освоить механику создания своих собственных умений в workshope.

Что мы будем делать
В этой статье мы реализуем ульт акса. Он может немного отличаться от оригинала, но эти отличия вполне можно назвать мелочами, которыми мы позволим себе пренебречь. Кроме того, я пренебрегаю различными тонкостями, что могут влиять на отработку ульта в тех или иных ситуациях, поскольку целью статьи является не создание точной копии умения, а демонстрация процесса создания умения, его прототипа. Естественно, кто-то может найти возможные ошибки или более простые способы решения тех или иных проблем - все это можно изложить в комментариях к посту.

Culling Blade
Итак, что же мы видим, когда используем аксом его ульт:
"ну, он подпрыгивает и бьет наотмашь своим топором противника, лопая его тушку и разбрызгивая на всех вокруг его кишки и кровь"

Применяем на цель, которая должна быть героем.
1. Применение на цель (target), которая является героем (unit hero)
1. Применение на цель (target), которая является героем (unit hero)
Это конечно не то что бы прям придирка НО  ульт акса используется на все ентити !не включая здания и прочие нестандартные как юниты цели(например: варды веника)!
Об чем это я - егошный ульт можно использовать и в крипа и (если не поправили или так и должно быть) в курьера. соответственно в эти рамки входят и герои
так что это не обязательно должен быть "unit hero"
реп'ки б
Жизнь без запятых чудесна.
И вабще. Люди должны понимать с полу-слова.

Оффлайн Илья

  • Супермодератор
  • 2131
  • Мощь: 21
Re: Кодим функции: ульт акса (axe_culling_blade)
« Ответ #11 : 12-05-2017, 17:45:17 »
Ну естественно не обязательно. Цель статьи не сделать копию ульта, а показать, как её делать.

Оффлайн MahouShoujo

  • Продвинутый
  • 201
  • Мощь: 3
Re: Кодим функции: ульт акса (axe_culling_blade)
« Ответ #12 : 12-05-2017, 21:51:17 »
Цитировать
GiveBaff

ha-ha.

Функция KillUnit из двух строчек которая вызывается в одном месте - зачем?

способность не перезаряжает себя при удачном использовании.

кулдаун не меняется с появлением аганима.

оригинальный ульт не делает :KillUnit а убивает настоящим дамагом.

тип урона в случае фейла неверный.

Цитировать
//хрен знает, что это за функция
оригинальный партикль ульты акса у меня не работал, взял гули
Туториал мастер просто

Оффлайн CryDeS

  • Друзья CG
  • 1212
  • Мощь: 12
Re: Кодим функции: ульт акса (axe_culling_blade)
« Ответ #13 : 13-05-2017, 05:54:08 »
ha-ha.

Функция KillUnit из двух строчек которая вызывается в одном месте - зачем?

способность не перезаряжает себя при удачном использовании.

кулдаун не меняется с появлением аганима.

оригинальный ульт не делает :KillUnit а убивает настоящим дамагом.

тип урона в случае фейла неверный.
Туториал мастер просто
Ульт акса как раз таки и делает KillUnit при достаточном хп. Именно по этому он и пробивает крест дазла. Ни один урон не пробьет крест дазла, ваш к.о.
И вот ты отметился во всех почти туторах и всех назвал раками, мб сам тогда напишешь парочку туторов? Или по чему предложишь новичкам учиться тогда?

---
upd
Кстати о, с выходом 7.0 такое больше нельзя делать caster:HasItemInInventory("item_ultimate_scepter") т.к она вернет true даже если айтем в бэкпаке.

Оффлайн ZLOY

  • Супермодератор
  • 452
  • Мощь: 6
Re: Кодим функции: ульт акса (axe_culling_blade)
« Ответ #14 : 13-05-2017, 08:51:18 »
Doc прав, не умеешь - не пиши.
+убийство не засчитывается герою таким способом.
Кстати о, с выходом 7.0 такое больше нельзя делать caster:HasItemInInventory("item_ultimate_scepter") т.к она вернет true даже если айтем в бэкпаке.
Код
function CDOTA_BaseNPC:HasItemInInventory(itemName,bIncludeBackpack)
local slots = bIncludeBackpack and 8 or 5

for i = 0, slots do
local item = self:GetItemInSlot(i)
if item and item:GetAbilityName() == itemName  then
return true
end
end
return false
end
« Последнее редактирование: 13-05-2017, 09:23:11 от ZLOY »