Скрипты на Lua.
Свои модули с настройками, команды чата, HUD и ESP — прямо из файла .lua. Скрипт перезагружается сам при каждом сохранении, а его модули живут в меню и пресетах наравне со встроенными.
Быстрый старт
Откройте меню клиента → Tools → Scripts и нажмите «Новый скрипт»: в папке .minecraft/atheryx/scripts появится файл из шаблона и откроется в редакторе. Сохраните его — скрипт загрузится, а его модуль появится в своём разделе меню.
-- @name Привет
-- @author вы
-- @version 1.0
-- @description Мой первый модуль
local m = module.create{
name = "Hello",
description = "Здоровается при включении",
category = "player",
}
local text = m:text("Текст", "Привет из Lua!")
m:on("enable", function()
chat.print(text:get())
end)
m:on("tick", function()
if player.health() < 6 then
client.notify("Hello", "Мало здоровья!")
end
end)- Шапка из комментариев
-- @name,@author,@version,@description— то, что видно в разделе Scripts. - Правка файла подхватывается за секунду: скрипт выгружается и грузится заново, а включённость и настройки его модулей сохраняются.
- Готовые примеры клиент кладёт в
scripts/examples— скопируйте любой вscripts, и он заработает. - Проверить что-то на лету — команда
.lua player.health()прямо в чате.
Как устроено
Жизненный цикл
- При загрузке выполняется весь файл (основной код). Модули, их настройки и команды заводятся именно здесь — из обработчиков
module.createне работает. - Модули скрипта — полноценные модули клиента: карточка в меню, клавиша, поиск, уведомления, сохранение в пресет. Выключенный или упавший скрипт не стирает их настройки из пресета.
- Скрипт можно выключить переключателем в разделе Scripts или командой
.script off имя— выбор помнится между запусками. - Перед выгрузкой (правка файла, выключение) модули скрипта выключаются, затем зовётся
events.on("unload").
Ошибки
Ошибка при загрузке показывается на карточке скрипта красным. Ошибка в обработчике модуля выключает модуль, в events.on — снимает обработчик, в таймере — снимает таймер. Каждая ошибка пишется в консоль раздела Scripts (там же вывод print) и в чат — не чаще раза в 5 секунд на одно место.
Команды в чате
.script listСкрипты и их состояние..script reload [имя]Перезагрузить один скрипт или все..script on|off <имя>Включить или выключить скрипт..script newНовый скрипт из шаблона — сразу в редакторе..script folderОткрыть папку скриптов..script logПоследние 10 строк консоли..lua <код>Выполнить строку Lua; выражение печатает значение: .lua world.tps(). Переменные живут до .lua reset.Модули
module.create{name, description, category, bind, …} → модульНовый модуль. name — до 32 символов, уникальное. category: combat, movement, render, player (по умолчанию), world. bind — клавиша: "R", "F6", "LSHIFT", "MOUSE4". Обработчики можно передать полями: tick = function() … end.module.create(name, [description], [category]) → модульТо же, короче.module.get(name) → модуль | nilЛюбой модуль клиента по имени — встроенный ("Aura") или из скрипта.module.list([category]) → {имена}Имена модулей, можно по разделу.module.is_enabled(name) → booleanВключён ли модуль.Методы модуля
m:on(event, fn)Обработчик события; работает, пока модуль включён. Повторный вызов заменяет обработчик. Только для своих модулей.m:off(event)Снять обработчик.m:enabled() → booleanВключён ли.m:toggle(), m:set_enabled(on)Переключить / включить или выключить.m:name(), m:description(), m:category()Имя, описание, раздел.m:bind() → "R" | nil, m:set_bind(key)Клавиша модуля; nil или "none" — без клавиши.m:setting(name) → настройка | nilНастройка по имени — в том числе у встроенных модулей: module.get("Aura"):setting("Дистанция"):set(3.2).m:settings() → {имена}Имена всех настроек.m:is_script() → booleanМодуль из скрипта или встроенный.Настройки
Настройки заводятся у своего модуля при загрузке, рисуются в его карточке и сохраняются в пресет. Каждый вызов возвращает объект настройки.
m:boolean(name, [default=false])Галочка. get() → boolean.m:slider(name, default, min, max, [step=1], [suffix])Ползунок: m:slider("Дистанция", 3, 1, 6, 0.1, " бл"). get() → число.m:range(name, from, to, min, max, [step=1], [suffix])Отрезок с двумя ручками. get() → from, to.m:mode(name, {варианты}, [default])Один из вариантов. get() → строка.m:multi(name, {варианты}, [{включённые}])Несколько вариантов. get() → список включённых, s:is(v), s:toggle(v).m:color(name, [default])Цвет с пикером. Без значения — следует теме клиента. get() → число ARGB.m:text(name, [default=""], [max=64])Строка. get() → string.m:keybind(name, [default])Своя клавиша. get() → имя или nil, s:down() — зажата ли сейчас.m:button(name, [label], fn)Кнопка-действие в карточке.Методы настройки
s:get(), s:set(value), s:reset()Значение, запись, сброс к умолчанию. Поля s.name и s.type (boolean, slider, range, mode, multi, color, text, keybind, button).s:describe(text)Подсказка под названием в меню.s:visible(fn | boolean)Показывать ли настройку: speed:visible(function() return mode:get() == "Быстрый" end).s:modes(), s:options(), s:min(), s:max(), s:press()Варианты режима и набора, границы ползунка, нажать кнопку.s:get()) — обычный стиль Lua; вызов через точку (s.get()) тоже работает.События
У модуля — m:on(name, fn), работают, пока модуль включён. У скрипта — events.on(name, fn) / events.off(name), работают, пока скрипт загружен. Таблицы событий с e:cancel() отменяют действие игры.
enable, disableМодуль включили / выключили (только у модуля).tickТик игрока, 20 раз в секунду, только в мире.tick_endКонец тика клиента (идёт и в меню).frameКаждый кадр в мире, между тиками.move(input)Ввод движения на тик: forward, back, left, right, jump, sneak, sprint, moving. Что поменяете в таблице — то и уйдёт в игру.render2d(frame)Рисование поверх интерфейса: width, height, mouse_x, mouse_y, partial.render_world(frame)То же, но под интерфейсом — для меток над игроками и блоками.render3d(frame)Отрисовка в мире: partial, camera_x/y/z.key(e), mouse(e)Клавиша / кнопка мыши без открытого экрана: e.key/e.button, e.name, e.action (press, release, repeat), e.pressed, e:cancel().chat(e)Сообщение в чате: e.text, e.sender (nil у системных), e:cancel() — скрыть.chat_send(e)Своё сообщение перед отправкой: e.text, e:cancel().attack(e)Удар: e.target (сущность), e.entity_id, e:cancel().packet_send(e), packet_receive(e)Пакеты: e.name, e.id, e.class, e.direction, e:cancel(). У движения — x, y, z, yaw, pitch, on_ground; у отбрасывания — entity_id, motion_x/y/z, self.join(e), leaveВход в мир (и смена измерения): e.dimension; выход.unloadСкрипт выгружается (только events.on).chat и packet_receive приходят из потока сети. Действия (chat.send, player.attack, m:toggle()) сами уходят в игровой поток, а вот рисовать там нельзя.Игрок
Таблица player. Без мира всё возвращает nil, а действия ничего не делают.
player.exists(), player.name(), player.entity()Есть ли игрок, ник, сам игрок как сущность.player.position([partial]) → x, y, zПозиция; с partial из кадра — плавная, для рисования.player.eye(), player.velocity(), player.rotation() → yaw, pitchГлаза, скорость за тик, углы камеры.player.speed() → блоков/сГоризонтальная скорость.player.health(), max_health(), absorption(), food(), saturation(), armor(), xp_level()Здоровье, золотые сердца, голод, насыщение, броня, уровень.on_ground(), sprinting(), sneaking(), in_water(), in_lava(), gliding(), using_item(), blocking(), on_fire(), riding()Состояние игрока (все — player.…()).fall_distance(), hurt_time(), attack_cooldown() → 0..1Высота падения, тики после урона, заряд удара.player.slot() → 1..9, player.set_slot(n)Слот хотбара.held_item(), offhand_item(), item(slot), inventory(), find_item(id) → слот | nilПредметы — таблицы {id, name, count, empty, damage, max_damage}. Слоты с 1: 1–9 хотбар, 10–36 рюкзак, 37–40 броня, 41 вторая рука. find_item("totem_of_undying").player.effects(), player.has_effect(id)Эффекты {id, level, duration} (длительность в тиках, -1 — бесконечно).gamemode(), ping(), dimension()Режим игры, пинг, измерение ("minecraft:overworld").player.target() → сущность | nilСущность под прицелом.player.looking_at() → {type, …}Что под прицелом: type = block / entity / miss, x, y, z, у блока — block, block_x/y/z, face, у сущности — entity.player.jump(), player.swing([hand]), player.set_sprinting(on)Прыжок (с земли), взмах рукой ("main"/"off"), бег.player.attack(entity), player.use_item([hand]), player.release_item()Ударить, использовать предмет, отпустить (лук, еда).player.set_rotation(yaw, pitch), player.look_at(x, y, z)Повернуть камеру. Тихий поворот только для сервера — rotation.set.player.set_velocity(x, y, z)Задать скорость.Мир и сущности
world.exists()Загружен ли мир.world.players() → {сущности}Игроки вокруг, включая вас (e:is_self()).world.entities([radius]) → {сущности}Все сущности, по желанию — в радиусе от игрока.world.entity(id)Сущность по id.world.block(x, y, z) → "minecraft:stone", world.is_air(x, y, z), world.is_solid(x, y, z)Блок в точке.world.time() → 0..23999, world.ticks()Время суток и счётчик тиков мира.world.dimension(), world.raining(), world.thundering()Измерение и погода.world.tps(), world.server() → адрес | nilTPS сервера, адрес (nil в одиночной игре).world.online() → {{name, ping, gamemode}}Список игроков из таба.Методы сущности
Сущность — лёгкая ссылка по id: если она ушла из мира, e:valid() вернёт false, остальные методы — nil. Сравнение a == b работает.
e:id(), e:uuid(), e:name(), e:type() → "minecraft:zombie", e:valid()Кто это.e:position([partial]), e:eye(), e:velocity(), e:rotation()Где и куда смотрит.e:distance([x, y, z])До игрока или до точки.e:box() → minX, minY, minZ, maxX, maxY, maxZ, e:width(), e:height(), e:age()Хитбокс и возраст в тиках.e:alive(), on_ground(), sneaking(), sprinting(), invisible(), on_fire(), in_water(), riding()Состояние.e:is_player(), is_living(), is_item(), is_self(), is_friend()Что это за сущность и друг ли.e:health(), max_health(), absorption(), armor(), hurt_time()Для живых существ.e:held_item(), offhand_item(), armor_items(), effects(), using_item(), blocking(), gliding()Снаряжение и действия.e:ping(), e:gamemode(), e:item()Пинг и режим игрока, предмет на земле.Рендер 2D
Таблица render — только в обработчиках render2d и render_world. Тот же движок, что у меню клиента: скругления, тени, размытие, чёткий текст. Координаты — в единицах интерфейса. Цвет — число 0xAARRGGBB или строка "#RRGGBB", "#AARRGGBB", "#RGB".
render.rect(x, y, w, h, color, [radius])Прямоугольник.render.outline(x, y, w, h, color, [thickness=1], [radius])Рамка.render.gradient(x, y, w, h, from, to, [vertical=true], [radius])Градиент.render.circle(x, y, r, color), render.ring(x, y, r, thickness, color)Круг и кольцо.render.arc(x, y, r, thickness, start, sweep, color)Дуга, углы в градусах.render.line(x1, y1, x2, y2, color, [width=1])Линия.render.shadow(x, y, w, h, sigma, [color])Мягкая тень или свечение.render.blur(x, y, w, h, [strength=12], [radius], [tint])Размытие того, что под областью.render.text(text, x, y, [size=10], [color], [align], [font]) → ширинаТекст. Вместо хвоста аргументов можно таблицу: {size, color, align, valign, font, shadow}. align: left/center/right, valign: top/center/bottom, font: regular/medium/semi/bold.render.text_width(text, [size], [font]), render.text_height([size], [font])Размеры строки.render.item(item, x, y, [size=16], [alpha])Иконка предмета: таблица из player.held_item() или id ("diamond_sword").render.scissor(x, y, w, h) / render.pop_scissor()Обрезка области.render.push(dx, dy, [scale], [ox], [oy]) / render.pop()Сдвиг и масштаб вокруг точки (ox, oy).render.alpha(a)Прозрачность всего дальнейшего, 0..1.render.project(x, y, z) → sx, sy | nilТочка мира на экране (nil — за камерой).render.width(), render.height(), render.partial()Размер кадра и доля тика.Ножницы и сдвиги, которые вы забыли снять, снимаются сами в конце обработчика.
Рендер 3D
Таблица render3d — в обработчике render3d. Фигуры видно сквозь стены; за обработчик — до 4096 штук.
render3d.box(x1, y1, z1, x2, y2, z2, fill, [outline])Бокс: заливка и рёбра (nil — без них).render3d.block(x, y, z, fill, [outline])Блок, в котором лежит точка.render3d.entity(e, fill, [outline], [grow])Хитбокс сущности, плавно за моделью.render3d.line(x1, y1, z1, x2, y2, z2, color)Линия.render3d.camera() → x, y, z, render3d.partial()Позиция камеры и доля тика.Чат и команды
chat.print(text)Сообщение себе с префиксом Atheryx; §-коды работают.chat.raw(text)То же без префикса.chat.send(text)Отправить в чат; со / — как команду, с . — команду клиента.chat.command(cmd)Серверная команда: chat.command("home").command.register(name, [description], fn(args, raw))Своя команда .name: args — слова после имени, raw — вся строка. Имена встроенных команд заняты.command.unregister(name)Снять команду.command.register("coords", "Мои координаты", function(args, raw)
local x, y, z = player.position()
chat.print(string.format("§fXYZ §7%.0f %.0f %.0f", x, y, z))
end)Таймеры и хранилище
timer.after(ms, fn) → idВызвать один раз через ms миллисекунд.timer.every(ms, fn) → idВызывать каждые ms.timer.cancel(id) → booleanСнять таймер.storage.get(key, [default])Значение, сохранённое между запусками игры.storage.set(key, value), storage.remove(key)Записать: числа, строки, boolean и таблицы из них.storage.keys(), storage.clear()Все ключи, очистить.Хранилище у каждого скрипта своё — файл scripts/.data/имя.json, пишется раз в секунду и при выгрузке.
Утилиты
print(...), script.log/warn/error(...)Строка в консоль раздела Scripts.script.id, script.name, script.author, script.version, script.reload()О себе и перезагрузка.require(name)Библиотека из scripts/lib/name.lua (require("utils.math") → lib/utils/math.lua). Правка библиотеки перезагружает скрипт.client.fps(), client.time(), client.in_game(), client.screen()FPS, время в мс, в мире ли, открытый экран: chat, container, pause, clickgui, other или nil.client.notify(title, [text]), client.theme(), client.name, client.versionУведомление клиента, цвет темы.color.rgb(r, g, b, [a]), color.hsv(h, s, v, [a]), color.hex(str)Цвет: каналы 0..255, оттенок в градусах, s и v 0..1.color.alpha(c, a), color.fade(c, f), color.mix(a, b, t), color.unpack(c) → r, g, b, a, color.theme()Прозрачность 0..255, множитель прозрачности 0..1, смешивание.math.clamp(x, min, max), math.lerp(a, b, t), math.round(x, [digits]), math.sign(x)В дополнение к обычному math.rotation.to(x, y, z) → yaw, pitchУглы от глаз на точку.rotation.set(yaw, pitch)Тихий поворот для сервера на этот тик (камера не двигается) — звать каждый тик, пока нужен.rotation.active(), rotation.yaw(), rotation.pitch(), rotation.wrap(deg)Идёт ли тихий поворот, его углы, нормализация угла.friends.is(name), friends.add(name), friends.remove(name), friends.list()Друзья клиента.input.key(name), input.mouse(button)Зажата ли клавиша ("LSHIFT", "R") или кнопка ("LEFT", "MOUSE4").input.key_code(name), input.key_name(code), input.cursor() → x, yКоды клавиш и позиция курсора.os.clock(), os.time(), os.date([format])Часы и дата; остального os нет.Ограничения
Скрипты работают в песочнице: им нельзя навредить компьютеру и повесить игру.
- Нет
io,debug,luajava,dofile,loadfileиosкроме часов — ни файлов, ни процессов, ни Java.loadпринимает только текст. - Обработчик события — до 60 мс, загрузка файла — до 2 с. Застрявший цикл обрывается, даже внутри
pcallи корутин. - Файл скрипта — до 1 МБ,
string.rep— до 4 млн символов, таймеров — до 512, хранилище — до 4096 ключей. - Рисовать можно только в обработчиках отрисовки и только из игрового потока.
- Каждый скрипт — в своём окружении: переменные и подмены
stringодного не видны другим.
Примеры
Все примеры лежат в scripts/examples: hello, autogg, hud, esp, commands, automessage.
Мини-HUD
local m = module.create{ name = "MiniHud", category = "render" }
local accent = m:color("Акцент") -- цвет темы
local size = m:slider("Размер", 10, 7, 16, 0.5)
m:on("render2d", function(frame)
local x, y, z = player.position(frame.partial)
local text = string.format("XYZ %.1f %.1f %.1f • %d FPS", x, y, z, client.fps())
local w = render.text_width(text, size:get(), "medium") + 16
render.blur(6, 6, w, 22, 10, 6)
render.rect(6, 6, w, 22, "#B0141820", 6)
render.rect(10, 11, 2, 12, accent:get(), 1)
render.text(text, 16, 17, { size = size:get(), font = "medium", valign = "center" })
end)Подсветка игроков
local m = module.create{ name = "SimpleESP", category = "render" }
local fill = m:color("Заливка", "#28FFFFFF")
local edge = m:color("Рёбра")
m:on("render3d", function(frame)
for _, p in ipairs(world.players()) do
if not p:is_self() then
render3d.entity(p, fill:get(), p:is_friend() and "#63FF8E" or edge:get())
end
end
end)Автоответ и тихий поворот
local m = module.create{ name = "LookAtNearest", category = "combat", bind = "G" }
local range = m:slider("Дальность", 4, 1, 6, 0.1, " бл")
m:on("tick", function()
local best, bestDistance
for _, p in ipairs(world.players()) do
local d = p:distance()
if not p:is_self() and not p:is_friend() and d <= range:get() and (not best or d < bestDistance) then
best, bestDistance = p, d
end
end
if best then
local x, y, z = best:eye()
rotation.set(rotation.to(x, y - 0.3, z))
end
end)
m:on("chat", function(e)
if e.sender and e.text:find("ping", 1, true) then
timer.after(500, function() chat.send("pong!") end)
end
end)