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, ownMoney: Long, items: List<PrivateStoreEntry>Пришло содержимое лавки на продажу. Приходит только в ответ на действие по продавцу, сам сервер список не шлёт. 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.

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