evolve
lua 5.4 · перезагрузка на лету

Lua API

Здесь основные вызовы, и каждый работает в текущей версии evolve.

91/ 91 методов

start

1

Скрипт — это файл .lua в папке scripts: в меню игры её открывает Настройки › Скрипты › «Открыть папку». Он возвращает таблицу колбэков и перечитывается при сохранении. Lua 5.4, одна общая VM на все скрипты, колбэки вызываются по очереди.

Что важно знать
  1. 1Рисуй в on_draw, играй в on_update. renderer.* вне кадра ничего не нарисует, orders.* внутри on_draw уйдут не тем путём.
  2. 2Сущность из entities.* годится на один кадр: сначала e:valid(), а сам список бери заново через entities.*.
  3. 3Первые ~10 секунд после запуска идёт обход схемы. Позиция, команда и здоровье читаются сразу, броня, способности и gamerules до конца обхода отдают 0.
  4. 4Пока client.local_hero() пустой, фильтры по команде считают врагом всех подряд — сначала герой, потом таргетинг.
  5. 5Координаты — мировые юниты Source: карта примерно от -8288 до 8288 по X и Y, точка это vec(x, y, z).
aim.luareturn { … }hud.luareturn { … }farm.luareturn { … }одна VM · Lua 5.4aim.on_updatehud.on_updatefarm.on_update↻ сохранён, перечитан
Скрипт — файл .lua, который возвращает таблицу колбэков. Все скрипты живут в одной VM, колбэки идут по очереди. Сохранил файл — он перечитан.
client.local_hero()→ Entity|nil

Герой локального игрока. nil, пока ты не в матче.

client.screen_size()→ number, number

Ширина и высота окна в пикселях.

client.cursor_world()→ Vector|nil

Точка под курсором на z-плоскости героя.

globals.curtime()→ number

Игровое время в секундах. Часы для любых таймеров.

globals.tickcount()→ number

Настоящий тик клиента, 30 Гц, монотонный.

ОсторожноПродолжает идти, когда игровое время на паузе.

vec(x, y, z)→ Vector

Точка или направление. Рядом vec2, angle и color(r, g, b, a) — все компоненты 0..255.

log.info(msg)

Строка в evolve.log. Первый инструмент, когда скрипт молчит.

Пример
-- scripts/hello.lua
local s = {}

function s.on_update()                  -- game logic
  local me = client.local_hero()
  if not (me and me:valid()) then return end
end

function s.on_draw()                    -- renderer.* only
  local me = client.local_hero()
  if not (me and me:valid()) then return end
  local x, y, on = renderer.world_to_screen(me:position())
  if on then
    renderer.text(font_default, x, y - 40, color(255, 255, 255, 255), "hello")
  end
end

return s

packs

2

Пак — это папка: pack.json, точка входа, модули, картинки и данные. Одна и та же папка работает брошенной в scripts/ и её же вы загружаете в магазин zip-архивом (Кабинет автора › Версии).

Что важно знать
  1. 1pack.json: version обязательна (вида 1.2.0), entry — файл входа (по умолчанию main.lua), name — имя в списке скриптов, evolve — сборка, под которую написан пак.
  2. 2Имя папки — имя пака. Модули грузятся от него: require("my_pack.lib.util") читает lib/util.lua. В магазине папка называется по слагу листинга.
  3. 3Путь с ./ — это папка вашего пака: renderer.load_image("./art/icon.png"), иконки меню "./art/…", fs.read("./data/x.json"). Имя папки знать не нужно.
  4. 4В паке можно .lua, картинки .png .jpg .bmp .tga .gif (до 4096 px по стороне) и данные .json .txt .md .csv. Zip до 16 МБ, до 1500 файлов.
  5. 5При загрузке каждый .lua компилируется, каждая картинка и json открываются. Ответ перечисляет все ошибки сразу, с файлом и строкой, а версию потом читает админ.
my_pack/pack.jsonmain.lualib/util.luaart/icon.pngdata/x.jsonscripts/zip → магазин
Пак — одна папка: брошенная в scripts/ она работает сразу, её же zip-архивом загружают в магазин. В pack.json обязательна version, путь с ./ ведёт в папку пака.
renderer.load_image(path)→ number

Картинка по пути: "./art/x.png" из своего пака, "panorama/images/…" из игры. Возвращает хэндл или -1; кэшируется по пути.

ОсторожноГрузите один раз при загрузке скрипта, не каждый кадр. После перезагрузки скриптов изменённый файл подхватывается сам.

renderer.load_image_bytes(bytes [, key])→ number

Картинка из байтов, которые скрипт уже держит (fs.read, http). Кэш по key, без него по содержимому.

Пример
-- scripts/my_pack/pack.json : { "version": "1.0.0", "name": "My Pack" }
-- scripts/my_pack/main.lua
local util = require("my_pack.lib.util")         -- scripts/my_pack/lib/util.lua
local icon = renderer.load_image("./art/icon.png")
local cfg = json.decode(fs.read("./data/defaults.json") or "{}")

local s = {}
function s.on_draw()
  local me = client.local_hero()
  if not (me and me:valid()) or icon < 0 then return end
  local x, y, on = renderer.world_to_screen(me:position())
  if on then renderer.image(icon, x - 16, y - 80, 32, 32) end
end
return s

events

3

Колбэки. Всё, что приходит из потоков движка, буферизуется и разбирается с пампа кадра — обработчик выполняется рядом с on_update, поэтому в нём можно звать любой вызов API.

Что важно знать
  1. 1Два способа подписаться: вернуть из файла таблицу { on_update = fn } или позвать events.register. Оба живут до перезагрузки скрипта.
  2. 2Данные события валидны только внутри колбэка — разбирай сразу, не складывай таблицу «на потом».
  3. 3Обработчик, который бросил, отцепляется и пишет трейс; diag.errors() показывает его в клиенте.
on_pre_updateon_updateframe_beginon_drawmenu::renderdrainpoll_keysбуфер
Один оборот — один кадр, колбэки идут в этом порядке. Событие ждёт в буфере и приходит на следующем drain.
events.register(name, fn)

Подписка на колбэк.

namestring
Имя колбэка, например "on_update".
fnfunction
Обработчик. Аргументы зависят от события.
"on_update" fn()

Логический тик, каждый кадр. Место для orders.* и всей игровой логики.

"on_draw" fn()

Кадр. renderer.* работает здесь и только здесь.

"on_game_event" fn(name, data)

Любое нативное событие: подписки на конкретные имена нет, приходит всё.

namestring
Имя события движка, например "dota_player_gained_level".
datatable
Все поля события по именам, снятые в момент срабатывания.

ОсторожноПриезжает с пампа кадра, не позже чем через кадр после срабатывания, в порядке возникновения.

"on_key" fn(vk, down)

Сырая клавиша из wndproc.

vknumber
Код клавиши Windows.
downboolean
Нажатие или отпускание.

ОсторожноВозврат игнорируется: скрипт не может съесть клавишу у игры.

"on_projectile" fn(p)

Создан tracking-снаряд.

ptable
handle, source, target, ability, move_speed, is_attack, dodgeable, expire_time, max_impact_time.

ОсторожноСущности внутри резолвятся в момент доставки: тот, кто умер в этом же кадре, придёт как nil.

"on_modifier_gained" fn(npc, mod)

Появился модификатор. Пара к нему — on_modifier_lost(npc, name).

npcEntity
Кому повесили.
modtable
name, stacks, duration и остальные поля модификатора.
"on_prepare_unit_orders" fn(o)

Приказ уходит на сервер. Верни false, чтобы отменить его; o.synthetic отделяет приказ скрипта от клика человека.

otable
order, target, position, ability, unit, queue, issuer, synthetic.

ОсторожноЕдинственный колбэк, который выполняется прямо на игровом потоке. Движок ждёт VM 2 мс и, не дождавшись, пропускает приказ без вето — считай это в events.veto_timeouts().

timers.after(sec, fn)→ number

Одноразовый таймер, возвращает id. Рядом timers.every(sec, fn) и timers.clear(id).

secnumber
Через сколько секунд игрового времени.
fnfunction
Что выполнить.
events.dropped()→ number

Сколько событий потеряно из-за переполненной очереди. Должно быть 0.

Пример
local s = {}

function s.on_projectile(p)
  local me = client.local_hero()
  if not (me and me:valid()) then return end
  if p.target == me and p.dodgeable then
    log.info("incoming, impact in " ..
      string.format("%.2f", p.max_impact_time - globals.curtime()))
  end
end

return s

entities

4

Списки сущностей. Снимок обновляется примерно 20 раз в секунду, userdata интернируется — сущность можно класть ключом в таблицу.

Что важно знать
  1. 1Фильтр — обычная таблица: { team = "enemy", alive = true, around = pos, radius = 600, class = "creep" }. Поле class сравнивается подстрокой имени класса.
  2. 2team — строка "enemy" или "ally", не число.
  3. 3Половина живых нейтралов на карте — это заранее заспавненная следующая волна лагеря: она неуязвима и отсеивается через e:is_waiting_to_spawn().
Фильтр — обычная таблица, team — строка "enemy" или "ally". Список — снимок на момент вызова: крип, который зашёл позже, попадёт в следующий запрос.
entities.all()→ Entity[]

Все сущности из снимка клиента.

entities.heroes(team)→ Entity[]

Герои, при желании только одной стороны.

teamstring?
"enemy", "ally" или ничего — тогда все.
entities.npcs(filter)→ Entity[]

Юниты по фильтру: крипы, иллюзии, саммоны, вышки.

filtertable?
team / alive / around / radius / class.
entities.in_radius(pos, radius, filter)→ Entity[]

Всё, что стоит в круге.

posVector
Центр круга.
radiusnumber
Радиус в мировых юнитах.
filtertable?
Тот же фильтр, что у npcs.
entities.closest(pos, filter)→ Entity|nil

Ближайший юнит, себя не считает.

posVector
Откуда мерить.
filtertable?
Тот же фильтр, что у npcs.
entities.by_class(name)→ Entity[]

Точное совпадение класса и всех наследников — не угадывание по имени.

namestring
Класс C++, например "C_DOTA_BaseNPC_Tower".
entities.trees()→ Entity[]

Деревья. Рядом entities.runes() и entities.physical_items() — предметы, лежащие на земле.

entities.get(index)→ Entity|nil

Сущность по индексу; entities.from_handle(h) — по хэндлу.

indexnumber
Индекс сущности.
Пример
local s = {}

function s.on_draw()
  local me = client.local_hero()
  if not (me and me:valid()) then return end

  for _, h in ipairs(entities.heroes("enemy")) do
    if h:valid() and h:is_alive() and not h:is_dormant() then
      renderer.circle_3d(h:position(), h:attack_range(), color(255, 80, 80, 120))
    end
  end
end

return s

entity

5

Объект сущности. Один и тот же тип для героя, крипа, способности и предмета — набор осмысленных методов зависит от того, что это.

Что важно знать
  1. 1Первым делом e:valid(). Исчезнувшая сущность отвечает дефолтами, а не ошибкой.
  2. 2Сущность в тумане (e:is_dormant()) заморожена на последнем виденном состоянии: позиция, здоровье и инвентарь у неё старые.
  3. 3Ключом в таблице держи e:handle() — индекс переиспользуется, хэндл несёт ещё и серийный номер.
e:valid()→ boolean

Сущность ещё есть в игре.

e:position()→ Vector

Мировая позиция. Синоним e:origin().

e:health()→ number

Текущее здоровье. Рядом max_health(), health_pct() (0..100), mana(), mana_pct().

e:is_alive()→ boolean

Жив. Рядом is_enemy(), is_ally(), is_illusion(), is_hero(), is_dormant().

e:distance(target)→ number

Расстояние в мировых юнитах.

targetVector|Entity
Точка или другая сущность.
e:ideal_speed()→ number

Точная текущая скорость со всеми модификаторами: ботинки, хаст, слоу, ауры.

Осторожноe:move_speed() — это базовая характеристика из сети, без модификаторов. Для движения нужен ideal_speed.

e:effective_bat()→ number

Базовое время атаки с учётом модификаторов — то, из чего считается таймлайн атаки. Рядом e:aps() и e:seconds_per_attack().

e:is_stunned()→ boolean

Бит состояния юнита. Тем же способом читаются is_silenced, is_rooted, is_hexed, is_disarmed, is_magic_immune, is_invulnerable.

ОсторожноБиты отвечают «да или нет» и не несут часов. Сколько осталось — считается по модификаторам, не по биту.

e:modifiers()→ table[]

Все модификаторы: name, class, stacks, duration, creation_time, die_time, ability, caster, parent, is_aura.

Осторожноparent — тот, на ком висит бафф, и для дебаффа это не кастер.

e:has_modifier(name)→ boolean

Быстрая проверка одного модификатора; e:get_modifier(name) вернёт его таблицу.

namestring
Внутреннее имя, например "modifier_bloodseeker_thirst".
e:handle()→ number

Полный 32-битный хэндл: индекс и серийный номер. Безопасный ключ между кадрами.

e:visible_to_enemy()→ boolean

Видит ли тебя противник. Надёжно для своих же юнитов.

abilities

6

Способности и предметы — тоже сущности, со своими методами: кулдаун, мана, дальность, каст-пойнт и данные из KV.

Что важно знать
  1. 1Кулдаун, мана и фаза каста реплицируются только владельцу. У чужого героя способность всегда выглядит готовой — это «нам не сказали», а не «не на кулдауне».
  2. 2e:get_ability(name) при промахе возвращает невалидную сущность, а не nil, и каждый вызов сканирует снимок. Резолвь один раз и держи в локальной переменной.
  3. 3Пробивает ли спелл BKB, решает ab:immunity_type() == 1, а не догадка по названию.
Кулдаун, мана и фаза каста приходят только владельцу: у чужого героя ab:is_ready() всегда true. Способность резолвь один раз через get_ability и держи в переменной.
hero:get_ability(name)→ Entity

Способность по имени.

namestring
Внутреннее имя, например "lion_impale".
hero:abilities()→ Entity[]

Все способности героя в порядке слотов.

ОсторожноСканирует снимок на каждый вызов — кэшируй, а не зови в цикле отрисовки.

hero:get_item(slot)→ Entity

Предмет в слоте; hero:items() отдаёт весь инвентарь.

slotnumber
Слот инвентаря, считая с нуля: 0..5 — то, что на герое, дальше рюкзак и стэш.
ab:cooldown()→ number

Остаток кулдауна в секундах, 0 — готово.

ab:is_ready()→ boolean

Уровень выше нуля и кулдаун истёк.

ab:mana_cost()→ number

Стоимость на текущем уровне; ab:ability_level() — сам уровень.

ab:cast_range()→ number

Дальность каста со всеми бонусами.

ab:cast_point()→ number

Каст-пойнт в секундах — задержка между приказом и срабатыванием.

ab:special(key, level)→ number

Любое число из данных способности, с талантами и аганимом.

keystring
Ключ из AbilityValues, например "duration" у lion_impale. Список ключей — в данных способности, не на глаз.
levelnumber?
Уровень; -1 или ничего — текущий.

ОсторожноУ неизученной способности текущий уровень даёт 0 — передавай уровень явно: ab:special("duration", 0).

ab:immunity_type()→ number|nil

0 никак, 1 пробивает по врагам, 2 не пробивает, 3 и 4 — то же для союзников.

Осторожноnil означает «не смогли прочитать», и это не то же самое, что 0.

ab:dispellable_type()→ number|nil

0 не диспеллится, 1 сильный диспелл, 2 обычный, 3 нет.

it:item_charges()→ number

Заряды предмета: бутылка, вэнд, урна. У способностей заряды считает ab:charges().

Пример
local s = {}
local hex

function s.on_update()
  local me = client.local_hero()
  if not (me and me:valid()) then return end

  hex = hex or me:get_ability("lion_voodoo")
  if not (hex and hex:valid() and hex:is_ready()) then return end
  if me:mana() < hex:mana_cost() then return end

  for _, h in ipairs(entities.heroes("enemy")) do
    if h:is_alive() and not h:is_magic_immune()
       and me:distance(h) < hex:cast_range() then
      orders.cast_target(hex, h)
      return
    end
  end
end

return s

orders

7

Настоящие приказы движка — тот же путь, которым идёт клик мышью. Отправляй их с кадра, из on_update.

Что важно знать
  1. 1У каждого приказа есть необязательный последний аргумент queue: orders.move_to(pos, true) встанет в очередь как шифт-клик.
  2. 2Приказ из чужого потока (колбэк GC, сетевой хук, свой поток) обязан лечь в буфер и уйти уже из on_update. Вызов движка из чужого потока вешает клиент на мьютексе.
  3. 3Приказ каждый кадр — это не «быстрее», а спам: сервер обрабатывает последний, а поток приказов виден в реплее.
Приказ уходит в игру как клик игрока: move_to ведёт героя в точку, attack — к цели. Отдавай приказы из on_update.
orders.move_to(pos, queue)

Приказ на движение. Рядом attack_move(pos) и move_to_target(entity).

posVector
Куда идти.
queueboolean?
Поставить в очередь вместо замены.
orders.attack(target, queue)

Атака по цели.

targetEntity
Кого атаковать.
queueboolean?
Поставить в очередь.
orders.cast_target(ability, target, queue)

Каст по юниту.

abilityEntity
Способность или предмет.
targetEntity
Цель каста.
queueboolean?
Поставить в очередь.
orders.cast_position(ability, pos, queue)

Каст по точке.

abilityEntity
Способность или предмет.
posVector
Точка каста.
queueboolean?
Поставить в очередь.
orders.cast_no_target(ability, queue)

Каст без цели; cast_toggle(ability) переключает тогл.

abilityEntity
Способность или предмет.
queueboolean?
Поставить в очередь.
orders.use_item(item, target)

Применить предмет.

itemEntity
Предмет из инвентаря.
targetEntity|Vector?
Цель или точка, если предмет их требует.
orders.stop()

Остановка; orders.hold() — держать позицию.

orders.unit_move_to(unit, pos, queue)

Приказ не своему герою, а подконтрольному юниту; так же устроен unit_attack.

unitEntity
Кем приказываем: саммон, курьер, второй мипо.
posVector
Куда идти.
queueboolean?
Поставить в очередь.
orders.raw(type, opts)

Полный набор dotaUnitOrder_t, если нужного именованного вызова нет.

typenumber
Код приказа из orders.types.
optstable
target, position, ability, unit, queue, issuer, show_effects.

renderer

8

Immediate-рендер на d3d11. Всё, что нарисовано в on_draw, живёт один кадр; мировые примитивы можно вместо этого зарегистрировать один раз, и их перепроецирует C++.

Что важно знать
  1. 1Цвет — color(r, g, b, a), 0..255. Текст и измерение принимают шрифт первым аргументом, глобальный font_default уже готов.
  2. 2world_to_screen возвращает три значения, и третье — «точка вообще на экране». Без этой проверки объекты за спиной рисуются зеркально.
  3. 3Шрифт грей заранее: метрики до перестройки атласа отдают фолбэк на 47 пикселей, и вёрстка по ним разъедется.
кадр игрыon_draw · renderer.*menu::render
Кадр собирается слоями: игра, поверх неё всё, что скрипт нарисовал в on_draw, сверху меню. renderer.* рисует только в on_draw.
renderer.world_to_screen(pos)→ number, number, boolean

Проекция точки: x, y и признак того, что она на экране.

posVector
Мировая точка.
renderer.text(font, x, y, col, str, align, shadow)

Текст на экране; renderer.measure_text(font, str) вернёт его ширину и высоту.

fontFont
Шрифт; годится глобальный font_default.
x, ynumber
Экранные координаты.
colColor
Цвет.
strstring
Текст.
alignstring?
Комбинация из "c" — по центру и "r" — вправо, "v" — по вертикали.
shadowboolean?
Тень под текстом.
renderer.load_font(name, size, weight, flags)→ Font

Шрифт, закэшированный по параметрам — звать повторно безопасно.

namestring
Имя системного шрифта.
sizenumber
Размер в пикселях.
weightnumber?
Насыщенность.
flagsnumber?
Флаги начертания.
renderer.line(x1, y1, x2, y2, col, thick)

Линия. Рядом rect, rect_outline, rounded_rect, triangle, poly и gradient.

renderer.circle_filled(x, y, r, col, segs)

Залитый круг на экране; renderer.circle рисует контур.

renderer.circle_3d(center, radius, col, segs, thick)

Кольцо на земле — индикатор дальности как в самой игре.

centerVector
Центр в мире.
radiusnumber
Радиус в мировых юнитах.
renderer.load_image(path)→ Image

Картинка; renderer.image(handle, x, y, w, h, tint) её рисует.

pathstring
Файл на диске или ресурс panorama из самой игры.
renderer.world_circle(id, center, radius, col, opts)

Мировая фигура, которую дальше ведёт C++: регистрируешь на тике, а перепроецируется она каждый кадр. Рядом world_line, world_poly, world_text, world_image и world_clear.

idstring
Идентификатор фигуры: та же строка заменяет прошлую.
centerVector
Центр.
radiusnumber
Радиус в мировых юнитах.
colColor
Цвет.
optstable?
ttl (по умолчанию 0.25 с), thickness, segments, filled, layer.

ОсторожноРегистрация — не отрисовка: звать её можно из on_update, и рисуется она под тем, что рисует on_draw.

Пример
local s = {}
local font = renderer.load_font("Verdana", 13)

function s.on_draw()
  local me = client.local_hero()
  if not (me and me:valid()) then return end

  for _, h in ipairs(entities.heroes("enemy")) do
    if h:is_alive() then
      local x, y, on = renderer.world_to_screen(h:position())
      if on then
        local w = renderer.measure_text(font, h:health() .. "")
        renderer.text(font, x - w / 2, y - 60, color(255, 90, 90, 255), h:health() .. "")
      end
    end
  end
end

return s

ui

9

Меню. Контролы объявляются один раз на уровне файла и переживают хот-релоад; значение читается через :get().

Что важно знать
  1. 1Первый аргумент — "вкладка.группа", разбивается по первой точке; без точки контрол уедет в группу general.
  2. 2Ключ настройки — это "вкладка.группа.подпись". Переименовал подпись — потерял всё, что под ней сохранено; для косметики есть c:caption().
  3. 3У combo и listbox :get() отдаёт индекс с нуля: options[c:get() + 1].
scripts.demoenabledrangemode▾lowmidhighon:get()→falsetruetruerange:get()→300600900mode:get()→012modes[mode:get() + 1]→"low""mid""high"
Контрол объявляется один раз на уровне файла, значение читается через :get(). У combo индекс с нуля, поэтому options[c:get() + 1].
ui.checkbox(group, label, default)→ Control

Галочка.

groupstring
"вкладка.группа".
labelstring
Подпись, она же часть ключа настройки.
defaultboolean
Значение при первом запуске.
ui.slider_int(group, label, min, max, default, opts)→ Control

Целочисленный слайдер; ui.slider_float добавляет число знаков после запятой.

min, maxnumber
Границы.
optstable?
{ unit = " ms", min_text = "Off", max_text = "Max" }.
ui.combo(group, label, items, default_index)→ Control

Выпадающий список; ui.multiselect и ui.listbox устроены так же.

itemsstring[]
Варианты.
default_indexnumber
Индекс с нуля.
ui.hotkey(group, label, key, mode)→ Control

Клавиша. :get() отвечает «зажата прямо сейчас», :key() — какой код на неё назначен.

keynumber
Код клавиши по умолчанию.
modestring?
"hold", "toggle", "always" или "off".
ui.button(group, label, fn)→ Control

Кнопка. Рядом ui.color_picker, ui.textbox, ui.label и ui.separator.

c:get()

Текущее значение; c:set(v) его пишет.

c:on_change(fn)→ Control

Реакция на изменение. Возвращает сам контрол, вызовы можно цеплять.

fnfunction
Вызывается без аргументов — значение читай через :get().
c:depend(other, expected, mode)→ Control

Подчинить контрол другому.

otherControl
Контрол-родитель.
expectednumber?
Значение, при котором этот активен, по умолчанию 1.
modestring?
"grey" — показать серым, "hide" — убрать со страницы.
ui.menu_open()→ boolean

Открыто ли меню — по нему гасят собственную отрисовку поверх.

Пример
local enabled = ui.checkbox("Draw.ESP", "Enemy circles", true)
local radius = ui.slider_int("Draw.ESP", "Radius", 100, 900, 400, { unit = " u" })

radius:depend(enabled)

local s = {}

function s.on_draw()
  if not enabled:get() then return end
  for _, h in ipairs(entities.heroes("enemy")) do
    if h:is_alive() then
      renderer.circle_3d(h:position(), radius:get(), color(255, 80, 80, 120))
    end
  end
end

return s

ml

10

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

Что важно знать
  1. 1Нужен onnxruntime.dll рядом с evolve.dll. ml.available() отвечает, есть ли он, ml.gpu_available() — доступен ли DirectML.
  2. 2net:run синхронный и стоит 0.2–0.5 мс. Если модель тяжелее, отдавай её в net:submit и забирай через net:poll в следующем кадре.
  3. 3history отдаёт сырые мировые значения. Нормализация — задача скрипта, а не буфера.
history.tensor(e, 6)[6, 11] · 20 Гцnet:run(t)0.2–0.5 мсnet:submit(t)poll · +1 кадр
net:run синхронный и стоит 0.2–0.5 мс; тяжёлую модель отдают в net:submit и забирают через net:poll в следующем кадре. history отдаёт сырые мировые значения, нормализует скрипт.
ml.load(path, provider)→ Model|nil

Модель, закэшированная по пути и провайдеру. nil при ошибке, причина в ml.last_error().

pathstring
Файл .onnx.
providerstring?
"cpu" по умолчанию, "gpu" или "auto".
net:run(input)→ Tensor|nil

Синхронный прогон.

inputTensor|Tensor[]
Один тензор или список по числу входов.
net:submit(input)

Асинхронный прогон на рабочем потоке; net:poll() забирает результат, net:busy() говорит, что он ещё считается.

ml.tensor(rows, cols)→ Tensor

Обнулённый тензор. Индексы с единицы: t:set(r, c, v), t:get(r, c), t:table().

rowsnumber
Число строк.
colsnumber?
Число столбцов; без него получится вектор.
history.tensor(entity, frames)→ Tensor|nil

Буфер движения [frames, 11], спереди дополненный нулями. Каналы перечислены в history.CH.

entityEntity
За кем следим.
framesnumber
Сколько кадров истории, от старых к новым.
history.set_rate(hz)

Частота общего буфера. history.track_all(true) расширит его со всех героев на всех юнитов — это заметно дороже.

hznumber
Частота записи, по умолчанию 20.

data

11

Файлы, настройки, лог и JSON. Всё, что скрипту нужно, чтобы пережить перезапуск клиента.

Что важно знать
  1. 1Свои файлы держи в fs.datadir(), а соседние файлы пакета ищи от fs.scriptdir(). Путь из имени чанка в ошибке доставать нельзя: Lua обрезает его на 60 символах.
  2. 2Для того, что пишется всю игру, есть fs.append. Цикл read-modify-write через fs.write квадратичен по размеру файла и теряет всё при падении на середине.
клиентfs.append(path, s)fs.read(path)→ 3 строки↻ перезапускdata/run.log{"t":72,"lh":1}{"t":95,"lh":2}{"t":131,"lh":3}
То, что пишется всю игру, ведут через fs.append в fs.datadir(): строки переживают перезапуск клиента. Цикл read-modify-write через fs.write квадратичен по размеру файла и теряет всё при падении на середине.
log.info(msg)

Строка в evolve.log; рядом log.warn и log.error.

fs.read(name)→ string|nil

Прочитать файл целиком; fs.exists и fs.listdir рядом.

namestring
Путь к файлу.
fs.append(name, str)

Дописать в конец, создав родительскую папку. Способ вести журнал по ходу матча.

fs.datadir()→ string

Папка data/ клиента, уже созданная. fs.scriptdir() отдаёт абсолютный путь к scripts/.

config.write_int(key, value)

Типизированное хранилище скрипта: read_int, read_float, read_bool, read_string и их write-пары, плюс config.has и config.delete.

keystring
Свой ключ, не связанный с меню.
valuenumber
Значение.
config.get(path)

Значение контрола меню по его пути; config.set(path, v) его пишет.

pathstring
"вкладка.группа.подпись" — тот же ключ, что в config.export().

ОсторожноБросает, если такого пути нет, — и это нарочно: иначе опечатка в пути молча выглядит как выключенная настройка.

json.encode(value)→ string

Таблица в строку; json.decode(str) — обратно.

diag.errors()→ table[]

Ошибки скриптов, свёрнутые по одной строке на уникальный сбой: event, script, msg, traceback, count. Рядом diag.profile() со временем каждого обработчика.