Ikar BotHardShiftПланыКонтактыСкачать
События

События

Полный поток событий: что приходит, когда приходит и какие поля несёт каждое событие.

На этой странице

События — это то, что происходит в игре само по себе: кто-то умер, выпал дроп, пришло приглашение в группу, сервер отклонил действие. Ждать их через опрос мира в цикле необязательно — есть поток.

Как читать события

Императивно — waitEvent

Самый частый способ: дождаться конкретного события после команды.

Kotlin
suspend inline fun <reified T : ScriptEvent> L2Bot.waitEvent(
    timeoutMs: Long,
    predicate: (T) -> Boolean = { true },
): T?

Ждёт первое событие типа T, подходящее под predicate, но не дольше timeoutMs миллисекунд. Возвращает событие или null, если не дождались.

Kotlin
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>. Подходит, когда нужно реагировать на поток событий постоянно, параллельно основной логике:

Kotlin
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 и так далее. События без полей — синглтоны, у них проверяется только тип.

Бой

СобытиеПоляКогда приходит
Diedoid: Int, sweepable: BooleanСущность умерла. sweepable — с трупа можно снять споил.
Revivedoid: IntСущность воскресла.
SkillUsedcasterOid: Int, targetOid: Int, skillId: Int, skillLevel: IntНачался каст умения.
SkillFailedskillId: Int, targetOid: IntКаст умения не состоялся.
SkillLandedcasterOid: Int, targetOids: List<Int>Умение подействовало на перечисленные цели.
CastCancelledoid: IntКаст прерван.
AttackStartedoid: IntСущность начала атаку.
AttackStoppedoid: IntСущность прекратила атаку.
StatsUpdatedoid: IntОбновились характеристики сущности (HP, MP и прочее).
GaugeSetupoid: Int, type: Int, time: Int, maxTime: IntПоявился прогресс-бар. type: 0 — каст, 1 — перезарядка. time и maxTime — в миллисекундах.

Цель

СобытиеПоляКогда приходит
TargetSelectedtargetOid: IntМой персонаж выбрал цель.
TargetCleared—Мой персонаж снял цель.
AnyTargetSelectedoid: Int, targetOid: IntКто-то другой выбрал цель.
AnyTargetClearedoid: IntКто-то другой снял цель.

Свой персонаж

СобытиеПоляКогда приходит
PlayerUpdatedblocks: IntОбновились данные персонажа. blocks — признак того, какие блоки данных пришли, как присылает сервер.
BuffsUpdated—Изменился список баффов.
SkillListUpdated—Изменился список умений.
SitStandChangedsitting: BooleanПерсонаж сел или встал.
VitalityPointsUpdatedpoints: IntИзменились очки vitality.
NevitPointsUpdatedpoints: IntИзменились очки Nevit.
NevitTimeUpdatedstarted: Boolean, timeLeftMs: IntТаймер Nevit запустился или обновился.
EnterWorldReceivedserverEpochSec: Int, tzOffsetSec: Int, daylightSec: IntВход в мир: серверное время и часовой пояс.
ManorListReceivedcastleIds: List<Int>Пришёл список манора.
AgitDecoInfoReceivedresidenceId: IntПришла информация о резиденции.
PledgeStatusUpdatedleaderId: Int, clanId: Int, crestId: Int, allyId: Int, allyCrestId: Int, largeCrestId: IntОбновился статус клана.
AllyCrestReceivedserverId: Int, crestId: Int, data: List<Byte>Пришёл герб альянса.
PrivateStoreSellTitleReceivedsellerOid: Int, title: StringПришло название лавки на продажу.
PrivateStoreBuyTitleReceivedbuyerOid: Int, title: StringПришло название лавки на скупку.
PrivateStoreListReceivedsellerOid: Int, storeType: Int, ownMoney: Long, items: List<PrivateStoreEntry>Пришло содержимое частной лавки — продажи, скупки или продажи комплектом, что именно, говорит storeType (коды в StoreType). Приходит только в ответ на действие по владельцу, сам сервер список не шлёт. ownMoney — ваши деньги, не владельца.
ShortcutRegisteredtype: Int, slot: Int, id: Int, sharedReuseGroup: Int, augOpt1: Int, augOpt2: Int, visualId: IntЗарегистрирован ярлык на панели.
ApSkillListReceivedenable: Boolean, resetSp: Long, abilityPoints: Int, usedAbilityPoints: Int, skills: List<ApSkillEntry>Пришёл список умений ability points.
ServerObjectAppearedobjectId: Int, displayId: Int, name: String, x: Int, y: Int, z: IntПоявился служебный объект сервера.
DominionWarStartedobjectId: Int, territoryId: Int, disguised: BooleanНачалась война территорий.
UnreadMailCountReceivedcount: IntПришло количество непрочитанных писем.
TutorialListReceiveddata: List<Byte>Пришли данные обучения.
TutorialClientEventEnabledeventId: IntВключено событие обучения.
TutorialHtmlClosed—Закрыто окно обучения.

Движение

СобытиеПоляКогда приходит
MoveStartedoid: IntСущность начала движение.
MoveStoppedoid: IntСущность остановилась.
MoveTypeChangedoid: Int, running: BooleanСущность переключилась между бегом и шагом.
AnyWaitTypeChangedoid: Int, sitting: BooleanСущность села или встала.
Teleportedoid: IntСущность телепортировалась.

Мир

СобытиеПоляКогда приходит
NpcAppearedoid: IntПоявился NPC или моб.
ObjectDisappearedoid: IntОбъект пропал из видимости.
ItemDroppedoid: Int, itemId: Int, isMy: BooleanВыпала вещь. isMy — закреплена за моей стороной.
ItemPickedUpoid: IntВещь подобрана.

Инвентарь

СобытиеПоляКогда приходит
InventoryUpdated—Инвентарь изменился.
InventoryLoadedtype: InventoryListTypeПришёл полный список: USER, PET или QUEST.
AutoSoulShotChangeditemId: Int, enabled: BooleanАвтошоты включены или выключены.

Пет

СобытиеПоляКогда приходит
PetSpawnedoid: Int, isOwn: BooleanПризван пет. isOwn — мой.
PetDismissedoid: IntПет отозван.
PetJoinedoid: IntПет присоединился к группе.
PetLeftoid: IntПет покинул группу.

Группа

СобытиеПоляКогда приходит
PartyUpdated—Состав или состояние группы изменились.
PartyMemberJoinedoid: Int, name: StringВ группу вошёл участник.
PartyInviteReceivedname: StringПришло приглашение в группу от name.
PartyLeft—Группа покинута или распалась.
PartyBuffsUpdatedoid: IntОбновились эффекты участника группы.

Диалоги и чат

СобытиеПоляКогда приходит
DialogReceivednpcOid: IntNPC открыл диалог. Текст — в bot.dialogText.
BoardReceivedpartId: StringПришла страница комьюнити-борды. Текст — в bot.boardText. Длинная страница режется сервером на части, поэтому событие приходит несколько раз подряд.
ConfirmDialogReceivedmsgId: Int, requestId: Int, sender: StringПришло окно-вопрос. Ответ — confirmDialog(msgId, requestId, accept).
CaptchaReceivedmsgId: Int, params: List<ConfirmDlgParam>, endTime: Int, requestId: IntПришла проверка (капча).
ClanInviteReceivedname: StringПришло приглашение в клан.
TradeRequestReceivedsenderOid: IntПришёл запрос на обмен.
ChatMessageReceivedsenderOid: Int, chatType: Int, senderName: String, message: StringСообщение в чате. Коды каналов — на странице Команды.

Системные

СобытиеПоляКогда приходит
SystemMessagemsgId: IntСистемное сообщение сервера.
ActionFailedskillId: Int, targetOid: Int, castingType: IntСервер отклонил действие.
MovementFailedcastingType: IntСервер отклонил перемещение.
NpcAggroChangedoid: Int, aggro: BooleanМоб начал или прекратил агриться на мою сторону.
CharSelectReady—Готов экран выбора персонажа.

Почта, аукцион, замки

СобытиеПоляКогда приходит
MailSent—Письмо отправлено.
MailListReceived—Пришёл список писем.
AuctionListReceived—Пришёл список лотов аукциона.
AuctionSellListReceived—Пришёл список своих лотов.
CastleInfoReceived—Пришла информация о замке.

Сырые пакеты

СобытиеПоляКогда приходит
PacketInhex: StringПакет сервер→клиент, как он пришёл с провода.
PacketOuthex: StringПакет клиент→сервер — команда бота либо действие игрока руками.

Единственные события, которых нет в игре: это не «что произошло», а «что было на проводе». Приходят, только пока включён захват — см. «Сырой трафик» в справочнике команд.

Через bot.events и waitEvent они не идут — у них свой поток, bot.packetsIn / bot.packetsOut, и читать их надо оттуда. Разделение не косметическое: в бою пакетов десятки в секунду, и в общем потоке они вытесняли бы игровые события у того, кто их ждёт. Заодно оттуда приезжает готовый Packet с opcode, bytes и направлением, а не голая hex-строка.

Вспомогательные типы

InventoryListType

Тип списка в событии InventoryLoaded: USER · PET · QUEST.

ConfirmDlgParam

Параметр в событии CaptchaReceived. Это sealed-тип с тремя вариантами:

ВариантПоляОписание
Textvalue: StringТекстовый параметр.
Numvalue: IntЧисловой параметр.
Unknowntype: IntНераспознанный параметр, известен только его тип.
Kotlin
val texts = captcha.params.filterIsInstance<ConfirmDlgParam.Text>().map { it.value }

ApSkillEntry

Запись в событии ApSkillListReceived.

ПолеТипОписание
skillIdIntId умения.
levelIntУровень.

PrivateStoreEntry

Одна позиция в событии PrivateStoreListReceived. В лавке скупки (storeType == StoreType.BUY) три поля читаются наоборот — смотри описания.

ПолеТипОписание
objectIdIntId конкретного экземпляра предмета. В лавке скупки всегда 0: этого предмета у владельца нет, он его только хочет.
itemIdIntId типа предмета.
countLongСколько выставлено на продажу; в лавке скупки — сколько владелец ещё готов купить.
priceLongЦена продавца за штуку; в лавке скупки — сколько он за неё заплатит.
basePriceLongСправочная цена предмета: разница price - basePrice показывает, насколько предложение отличается от базового.
bodyPartIntМаска слота экипировки, 0 у ненадеваемых предметов.
enchantLevelIntУровень заточки. Без него позиции с одинаковым itemId неразличимы.