События
Полный поток событий: что приходит, когда приходит и какие поля несёт каждое событие.
На этой странице
События — это то, что происходит в игре само по себе: кто-то умер, выпал дроп, пришло приглашение в группу, сервер отклонил действие. Ждать их через опрос мира в цикле необязательно — есть поток.
Как читать события
Императивно — waitEvent
Самый частый способ: дождаться конкретного события после команды.
suspend inline fun <reified T : ScriptEvent> L2Bot.waitEvent(
timeoutMs: Long,
predicate: (T) -> Boolean = { true },
): T?Ждёт первое событие типа T, подходящее под predicate, но не дольше timeoutMs миллисекунд. Возвращает событие или null, если не дождались.
bot.attack(mob)
val died = bot.waitEvent<ScriptEvent.Died>(15_000) { it.oid == mob.oid }
if (died != null) bot.log("моб убит") else bot.log("не дождался")Реактивно — bot.events
bot.events — это Flow<ScriptEvent>. Подходит, когда нужно реагировать на поток событий постоянно, параллельно основной логике:
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.collect
override suspend fun run(bot: L2Bot) = coroutineScope {
launch {
bot.events.filterIsInstance<ScriptEvent.ChatMessageReceived>()
.collect { if (it.message.contains("помоги")) bot.log("зовут: ${it.senderName}") }
}
while (true) { /* основная логика */ delay(500) }
}Во встроенном редакторе операторы Flow недоступны. Там пишется только тело скрипта, а список импортов фиксирован и дополнить его нельзя — filterIsInstance, collect и прочие операторы потока требуют импорта kotlinx.coroutines.flow.*. В редакторе используй waitEvent, он работает без дополнительных импортов; полный доступ к потоку есть в плагине-jar.
Параллельная обработка событий требует своей корутины: bot.events не завершается сам, и collect в основном теле заблокирует скрипт навсегда.
Что важно знать про поток
- Поток «живой». События, случившиеся до того, как скрипт начал слушать, теряются — истории нет.
waitEventподписывается в момент вызова. Если событие приходит очень быстро, между командой иwaitEventвозможна гонка: ответ уже пришёл, а слушать ещё не начали. Для быстрых реакций подпишись заранее — запустиcollectв отдельной корутине — либо проверяй результат по состоянию мира.- Буфер конечный. Если обработчик в
collectработает долго, самые старые события отбрасываются. Тяжёлую работу выноси изcollect.
Чаще всего нужны
Died · SkillUsed · SkillLanded · TargetSelected · NpcAggroChanged · ItemDropped · InventoryUpdated · DialogReceived · ConfirmDialogReceived · PartyInviteReceived · ChatMessageReceived · SystemMessage · ActionFailed
Ниже — полный список. Все события — вложенные типы ScriptEvent, обращение к ним ScriptEvent.Died и так далее. События без полей — синглтоны, у них проверяется только тип.
Бой
| Событие | Поля | Когда приходит |
|---|---|---|
Died | oid: Int, sweepable: Boolean | Сущность умерла. sweepable — с трупа можно снять споил. |
Revived | oid: Int | Сущность воскресла. |
SkillUsed | casterOid: Int, targetOid: Int, skillId: Int, skillLevel: Int | Начался каст умения. |
SkillFailed | skillId: Int, targetOid: Int | Каст умения не состоялся. |
SkillLanded | casterOid: Int, targetOids: List<Int> | Умение подействовало на перечисленные цели. |
CastCancelled | oid: Int | Каст прерван. |
AttackStarted | oid: Int | Сущность начала атаку. |
AttackStopped | oid: Int | Сущность прекратила атаку. |
StatsUpdated | oid: Int | Обновились характеристики сущности (HP, MP и прочее). |
GaugeSetup | oid: Int, type: Int, time: Int, maxTime: Int | Появился прогресс-бар. type: 0 — каст, 1 — перезарядка. time и maxTime — в миллисекундах. |
Цель
| Событие | Поля | Когда приходит |
|---|---|---|
TargetSelected | targetOid: Int | Мой персонаж выбрал цель. |
TargetCleared | — | Мой персонаж снял цель. |
AnyTargetSelected | oid: Int, targetOid: Int | Кто-то другой выбрал цель. |
AnyTargetCleared | oid: Int | Кто-то другой снял цель. |
Свой персонаж
| Событие | Поля | Когда приходит |
|---|---|---|
PlayerUpdated | blocks: Int | Обновились данные персонажа. blocks — признак того, какие блоки данных пришли, как присылает сервер. |
BuffsUpdated | — | Изменился список баффов. |
SkillListUpdated | — | Изменился список умений. |
SitStandChanged | sitting: Boolean | Персонаж сел или встал. |
VitalityPointsUpdated | points: Int | Изменились очки vitality. |
NevitPointsUpdated | points: Int | Изменились очки Nevit. |
NevitTimeUpdated | started: Boolean, timeLeftMs: Int | Таймер Nevit запустился или обновился. |
EnterWorldReceived | serverEpochSec: Int, tzOffsetSec: Int, daylightSec: Int | Вход в мир: серверное время и часовой пояс. |
ManorListReceived | castleIds: List<Int> | Пришёл список манора. |
AgitDecoInfoReceived | residenceId: Int | Пришла информация о резиденции. |
PledgeStatusUpdated | leaderId: Int, clanId: Int, crestId: Int, allyId: Int, allyCrestId: Int, largeCrestId: Int | Обновился статус клана. |
AllyCrestReceived | serverId: Int, crestId: Int, data: List<Byte> | Пришёл герб альянса. |
PrivateStoreSellTitleReceived | sellerOid: Int, title: String | Пришло название лавки на продажу. |
PrivateStoreBuyTitleReceived | buyerOid: Int, title: String | Пришло название лавки на скупку. |
PrivateStoreListReceived | sellerOid: Int, ownMoney: Long, items: List<PrivateStoreEntry> | Пришло содержимое лавки на продажу. Приходит только в ответ на действие по продавцу, сам сервер список не шлёт. ownMoney — ваши деньги, не продавца. |
ShortcutRegistered | type: Int, slot: Int, id: Int, sharedReuseGroup: Int, augOpt1: Int, augOpt2: Int, visualId: Int | Зарегистрирован ярлык на панели. |
ApSkillListReceived | enable: Boolean, resetSp: Long, abilityPoints: Int, usedAbilityPoints: Int, skills: List<ApSkillEntry> | Пришёл список умений ability points. |
ServerObjectAppeared | objectId: Int, displayId: Int, name: String, x: Int, y: Int, z: Int | Появился служебный объект сервера. |
DominionWarStarted | objectId: Int, territoryId: Int, disguised: Boolean | Началась война территорий. |
UnreadMailCountReceived | count: Int | Пришло количество непрочитанных писем. |
TutorialListReceived | data: List<Byte> | Пришли данные обучения. |
TutorialClientEventEnabled | eventId: Int | Включено событие обучения. |
TutorialHtmlClosed | — | Закрыто окно обучения. |
Движение
| Событие | Поля | Когда приходит |
|---|---|---|
MoveStarted | oid: Int | Сущность начала движение. |
MoveStopped | oid: Int | Сущность остановилась. |
MoveTypeChanged | oid: Int, running: Boolean | Сущность переключилась между бегом и шагом. |
AnyWaitTypeChanged | oid: Int, sitting: Boolean | Сущность села или встала. |
Teleported | oid: Int | Сущность телепортировалась. |
Мир
| Событие | Поля | Когда приходит |
|---|---|---|
NpcAppeared | oid: Int | Появился NPC или моб. |
ObjectDisappeared | oid: Int | Объект пропал из видимости. |
ItemDropped | oid: Int, itemId: Int, isMy: Boolean | Выпала вещь. isMy — закреплена за моей стороной. |
ItemPickedUp | oid: Int | Вещь подобрана. |
Инвентарь
| Событие | Поля | Когда приходит |
|---|---|---|
InventoryUpdated | — | Инвентарь изменился. |
InventoryLoaded | type: InventoryListType | Пришёл полный список: USER, PET или QUEST. |
AutoSoulShotChanged | itemId: Int, enabled: Boolean | Автошоты включены или выключены. |
Пет
| Событие | Поля | Когда приходит |
|---|---|---|
PetSpawned | oid: Int, isOwn: Boolean | Призван пет. isOwn — мой. |
PetDismissed | oid: Int | Пет отозван. |
PetJoined | oid: Int | Пет присоединился к группе. |
PetLeft | oid: Int | Пет покинул группу. |
Группа
| Событие | Поля | Когда приходит |
|---|---|---|
PartyUpdated | — | Состав или состояние группы изменились. |
PartyMemberJoined | oid: Int, name: String | В группу вошёл участник. |
PartyInviteReceived | name: String | Пришло приглашение в группу от name. |
PartyLeft | — | Группа покинута или распалась. |
PartyBuffsUpdated | oid: Int | Обновились эффекты участника группы. |
Диалоги и чат
| Событие | Поля | Когда приходит |
|---|---|---|
DialogReceived | npcOid: Int | NPC открыл диалог. Текст — в bot.dialogText. |
BoardReceived | partId: String | Пришла страница комьюнити-борды. Текст — в bot.boardText. Длинная страница режется сервером на части, поэтому событие приходит несколько раз подряд. |
ConfirmDialogReceived | msgId: Int, requestId: Int, sender: String | Пришло окно-вопрос. Ответ — confirmDialog(msgId, requestId, accept). |
CaptchaReceived | msgId: Int, params: List<ConfirmDlgParam>, endTime: Int, requestId: Int | Пришла проверка (капча). |
ClanInviteReceived | name: String | Пришло приглашение в клан. |
TradeRequestReceived | senderOid: Int | Пришёл запрос на обмен. |
ChatMessageReceived | senderOid: Int, chatType: Int, senderName: String, message: String | Сообщение в чате. Коды каналов — на странице Команды. |
Системные
| Событие | Поля | Когда приходит |
|---|---|---|
SystemMessage | msgId: Int | Системное сообщение сервера. |
ActionFailed | skillId: Int, targetOid: Int, castingType: Int | Сервер отклонил действие. |
MovementFailed | castingType: Int | Сервер отклонил перемещение. |
NpcAggroChanged | oid: Int, aggro: Boolean | Моб начал или прекратил агриться на мою сторону. |
CharSelectReady | — | Готов экран выбора персонажа. |
Почта, аукцион, замки
| Событие | Поля | Когда приходит |
|---|---|---|
MailSent | — | Письмо отправлено. |
MailListReceived | — | Пришёл список писем. |
AuctionListReceived | — | Пришёл список лотов аукциона. |
AuctionSellListReceived | — | Пришёл список своих лотов. |
CastleInfoReceived | — | Пришла информация о замке. |
Сырые пакеты
| Событие | Поля | Когда приходит |
|---|---|---|
PacketIn | hex: String | Пакет сервер→клиент, как он пришёл с провода. |
PacketOut | hex: String | Пакет клиент→сервер — команда бота либо действие игрока руками. |
Единственные события, которых нет в игре: это не «что произошло», а «что было на проводе». Приходят, только пока включён захват — см. «Сырой трафик» в справочнике команд.
Через bot.events и waitEvent они не идут — у них свой поток, bot.packetsIn / bot.packetsOut, и читать их надо оттуда. Разделение не косметическое: в бою пакетов десятки в секунду, и в общем потоке они вытесняли бы игровые события у того, кто их ждёт. Заодно оттуда приезжает готовый Packet с opcode, bytes и направлением, а не голая hex-строка.
Вспомогательные типы
InventoryListType
Тип списка в событии InventoryLoaded: USER · PET · QUEST.
ConfirmDlgParam
Параметр в событии CaptchaReceived. Это sealed-тип с тремя вариантами:
| Вариант | Поля | Описание |
|---|---|---|
Text | value: String | Текстовый параметр. |
Num | value: Int | Числовой параметр. |
Unknown | type: Int | Нераспознанный параметр, известен только его тип. |
val texts = captcha.params.filterIsInstance<ConfirmDlgParam.Text>().map { it.value }ApSkillEntry
Запись в событии ApSkillListReceived.
| Поле | Тип | Описание |
|---|---|---|
skillId | Int | Id умения. |
level | Int | Уровень. |
PrivateStoreEntry
Одна позиция в событии PrivateStoreListReceived.
| Поле | Тип | Описание |
|---|---|---|
objectId | Int | Id конкретного экземпляра предмета. |
itemId | Int | Id типа предмета. |
count | Long | Сколько выставлено на продажу. |
price | Long | Цена продавца за штуку. |
basePrice | Long | Справочная цена предмета: разница price - basePrice показывает, насколько предложение отличается от базового. |
bodyPart | Int | Маска слота экипировки, 0 у ненадеваемых предметов. |