Lua-скрипты в Dota 2 запускаются не “просто потому что вы их написали”, а только когда Dota сама загружает Lua в нужном месте: через Lua-скрипты abilities/modifiers или через datadriven (когда в файлах конфигурации прописан RunScript на конкретную функцию в Lua). Поэтому ответ на вопрос “как использовать Lua скрипты в доте” сводится к тому, чтобы правильно подключить скрипт к игровым сущностям (модификаторам, способностям, item-ам) и убедиться, что код исполняется в клиенте/сервере там, где это предусмотрено.


Какие Lua-скрипты вообще существуют в Dota 2

В реальности в Dota 2 вы работаете с Lua как с прикладным слоем, который вызывает игровое окружение Dota. Почти всегда речь про один из вариантов:

Вариант Где подключается Что реально запускает Dota Для чего обычно
Ability / Modifier Script В описании способности/модификатора (внутри datadriven или KV) Dota грузит Lua, когда создаётся способность/модификатор Логика скиллов, пассивки, поведение модификаторов
Datadriven RunScript В .txt конфигурации datadriven (item/ability) Dota вызывает указанную Lua-функцию при событии Сценарии предметов, кастомных реакций на события
Отладка Lua в инструментах Через встроенные инструменты/консольные команды разработчика Инструменты загружают/выполняют скрипты Проверка Lua-кода, диагностика

Главный принцип: Dota должна “узнать” ваш Lua-файл и вашу функцию из KV/datadriven. Самостоятельно “инжектить Lua VM” или подменять окружение — это другая история (и в игре обычно не даст результата, если вы не повторяете именно то API и именно тот рантайм Dota).


Папки и файлы: где хранить Lua

Самый частый путь для логики предметов/модификаторов в кастомной datadriven-части:

Назначение Типичный путь
Lua предметов/модификаторов в datadriven scripts/vscripts/items/ (или другая подпапка внутри vscripts)
KV/Datadriven описания файлы в структуре npc_*.txt, где Dota читает datadriven-описания

В примере battle fury (datadriven) Lua лежит как:

  • scripts/vscripts/items/item_bfury.lua

А в datadriven указывается путь:

  • "ScriptFile" "items/item_bfury.lua"

Обратите внимание: в ScriptFile путь задаётся относительно vscripts (то есть без scripts/vscripts/ в начале).


Как Dota “понимает”, что надо выполнить ваш Lua (RunScript)

События в datadriven подключают Lua через блок RunScript. Примерная схема выглядит так:

  • В .txt datadriven вы объявляете модификатор.
  • Для нужного события (например, OnCreated, OnDestroy, OnIntervalThink) добавляете RunScript.
  • Внутри RunScript указываете:
  • ScriptFile (Lua-файл)
  • Function (имя функции в Lua)

В battle fury это сделано так:

"OnCreated"
{
  "RunScript"
  {
    "ScriptFile" "items/item_bfury.lua"
    "Function" "modifier_item_bfury_datadriven_on_created"
  }
}

А для таймера:

"OnIntervalThink"
{
  "RunScript"
  {
    "ScriptFile" "items/item_bfury.lua"
    "Function" "modifier_item_bfury_datadriven_on_interval_think"
  }
}

То есть Dota сама вызывает Lua, когда:
1) модификатор создан/удалён/получил tick,
2) срабатывает событие,
3) выполнено условие RunScript.


Где писать код: какие функции ожидает Dota

В Lua вы пишете именно те функции, которые прописаны в Function в RunScript.

Для модификатора предмета в примере battle fury используются:

  • modifier_item_bfury_datadriven_on_created(keys)
  • modifier_item_bfury_datadriven_on_destroy(keys)
  • modifier_item_bfury_datadriven_on_interval_think(keys)
  • modifier_item_bfury_datadriven_cleave_on_interval_think(keys)

Обычно Dota передаёт в аргумент keys контекст (кастер, способность, параметры события и т.д.). В вашем Lua доступно то, что Dota кладёт в keys.

Пример логики (смысл тот же, что в исходном материале):

function modifier_item_bfury_datadriven_on_created(keys)
  if not keys.caster:IsRangedAttacker() then
    keys.ability:ApplyDataDrivenModifier(
      keys.caster,
      keys.caster,
      "modifier_item_bfury_datadriven_cleave",
      { duration = -1 }
    )
  end
end

Ключевой момент: если вы назвали функцию иначе, чем в Function — Dota её не найдёт. Если вы ожидаете поля в keys, которых там нет — логика сломается.


Как правильно вызывать модификаторы из Lua

В datadriven логике обычно используется:

Что нужно Как делают
Присвоить модификатор юниту keys.ability:ApplyDataDrivenModifier(caster, target, "modifier_name", {duration = ...})
Снять модификатор caster:RemoveModifierByName("modifier_name")
Проверять состояние caster:IsRangedAttacker(), caster:HasModifier("modifier_name")

В battle fury модификатор клива выдаётся только если герой ближнего боя, и снимается при смене формы (у юнитов типа Troll Warlord это критично).


Клиент/сервер: почему “оно не работает” у многих

Самая частая причина, когда люди пытаются “использовать Lua скрипты в доте” не через правильные точки входа: они ожидают, что Lua вызовется и в том месте, где Dota этого не делает.

Практические правила:

  • Ваш Lua код для способностей/модификаторов должен быть подключён через datadriven/KV так, чтобы Dota сама его грузила и вызывала.
  • Если вы запускаете Lua вне этого механизма (например, через внешний injector/DLL), то вы не получаете нужное игровое API и нужный контекст исполнения Dota. Тогда Entities, GetLocalPlayer и подобные вещи просто не существуют в том окружении, которое вы создали. Даже если Lua “запускается”, игровые сущности и методы не появятся сами.

Именно поэтому правильный путь для “обычного использования Lua в Dota” — это datadriven + RunScript или скрипты abilities/modifiers, а не самостоятельная VM со своим окружением.


Частые ошибки новичков и как их избежать

Ошибка Почему так Как исправить
“Скрипт есть, но в игре ничего не происходит” Dota не знает, что грузить, или событие не наступило, или путь/имя функции не совпало Проверьте ScriptFile, Function, имя модификатора и что модификатор реально создаётся
“Функции вызываются, но логика не выполняется” Условия проверяются не так (например, вы перепутали IsRangedAttacker() и инверсию) Сверьте условия типов атаки/формы юнита
“Код ломается на попытке использовать Entities/GetLocalPlayer” Это не тот API и не тот рантайм Dota, который доступен в встроенных Lua-скриптах Используйте функции Dota через keys/контекст abilities/modifiers
“Пишу OnIntervalThink, но всё не тикет” Не задан ThinkInterval или модификатор не активен/не создан Убедитесь, что в datadriven есть ThinkInterval и что модификатор действительно существует

Откуда брать основу: обучение через рабочие примеры

Самый надёжный способ научиться — брать пример datadriven предмета, где:
- есть секция модификатора,
- есть RunScript на события,
- есть ApplyDataDrivenModifier и RemoveModifierByName.

Battle fury из примера как раз такой: там через OnCreated выдаётся модификатор клива, через OnDestroy он убирается, а через ThinkInterval происходит контроль смены ближний/дальний.


Небольшая “карта” процесса: что сделать, чтобы Lua реально заработал

Шаг Что сделать Результат
Подготовить datadriven/KV Создать/объявить модификатор или способность и привязать к ней события Dota начинает вызывать ваши обработчики
Создать Lua-файл в нужной папке Например scripts/vscripts/items/item_bfury.lua Код доступен по пути ScriptFile
Добавить RunScript в события ScriptFile + Function Dota вызывает вашу Lua-функцию в нужный момент
В Lua использовать контекст keys keys.caster, keys.ability, ApplyDataDrivenModifier Модификаторы и логика начинают работать как задумано

Источники (официальные и максимально релевантные)

  • Valve Developer Community: Lua Abilities and Modifiers (как устроены abilities/modifiers Lua)
    https://developer.valvesoftware.com/wiki/Ru/Dota_2_Workshop_Tools/Lua_Abilities_and_Modifiers
  • Valve Developer Community: Debugging Lua scripts (как отлаживать Lua в контексте Dota)
    https://developer.valvesoftware.com/wiki/Ru/Dota_2_Workshop_Tools/Scripting/Debugging_Lua_scripts

Итог

Чтобы использовать Lua-скрипты в Dota 2, нужно не “выполнить Lua где-то”, а подключить его к игровому механизму, чтобы Dota сама загрузила ваш файл и вызвала ваши функции. Самый понятный рабочий паттерн — datadriven + RunScript, как в примере battle fury: OnCreated/OnDestroy/OnIntervalThink в .txt вызывают нужные функции в вашем Lua-файле, а дальше вы управляете модификаторами через ApplyDataDrivenModifier и RemoveModifierByName.