Ikar BotHardShiftRoadmapContactGet
Events

Events

The full event stream: what arrives, when it arrives, and the fields each event carries.

On this page

Events are the things that happen in the game on their own: someone died, an item dropped, a party invitation arrived, the server rejected an action. You don't have to poll the world in a loop for them — there is a stream.

Reading events

Imperatively — waitEvent

The most common approach: wait for a specific event after a command.

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

Waits for the first event of type T matching predicate, but no longer than timeoutMs milliseconds. Returns the event, or null if it never arrived.

Kotlin
bot.attack(mob)
val died = bot.waitEvent<ScriptEvent.Died>(15_000) { it.oid == mob.oid }
if (died != null) bot.log("mob killed") else bot.log("gave up waiting")

Reactively — bot.events

bot.events is a Flow<ScriptEvent>. Use it when you need to react to a stream of events continuously, alongside your main logic:

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("help")) bot.log("called by ${it.senderName}") }
    }

    while (true) { /* main logic */ delay(500) }
}

Flow operators are not available in the built-in editor. There you write only the script body and the import list is fixed and cannot be extended — filterIsInstance, collect and the other stream operators need kotlinx.coroutines.flow.*. Use waitEvent in the editor, it needs no extra imports; full stream access is available in a jar plugin.

Handling events in parallel needs its own coroutine: bot.events never completes, so a collect in the main body would block the script forever.

What to know about the stream

  • The stream is live. Events that happened before the script started listening are lost — there is no history.
  • waitEvent subscribes at the moment of the call. If an event arrives very quickly there is a race between the command and waitEvent: the answer is already in while nobody is listening yet. For fast reactions subscribe up front — run a collect in a separate coroutine — or check the world state instead.
  • The buffer is finite. If your collect handler is slow, the oldest events are dropped. Keep heavy work out of collect.

The ones you'll actually use

Died · SkillUsed · SkillLanded · TargetSelected · NpcAggroChanged · ItemDropped · InventoryUpdated · DialogReceived · ConfirmDialogReceived · PartyInviteReceived · ChatMessageReceived · SystemMessage · ActionFailed

The full list follows. All events are nested types of ScriptEvent, referenced as ScriptEvent.Died and so on. Events with no fields are singletons — only their type is checked.

Combat

EventFieldsWhen it arrives
Diedoid: Int, sweepable: BooleanAn entity died. sweepable — the corpse can be swept.
Revivedoid: IntAn entity was revived.
SkillUsedcasterOid: Int, targetOid: Int, skillId: Int, skillLevel: IntA skill cast started.
SkillFailedskillId: Int, targetOid: IntA skill cast did not happen.
SkillLandedcasterOid: Int, targetOids: List<Int>A skill took effect on the listed targets.
CastCancelledoid: IntA cast was interrupted.
AttackStartedoid: IntAn entity started attacking.
AttackStoppedoid: IntAn entity stopped attacking.
StatsUpdatedoid: IntAn entity's stats changed (HP, MP and so on).
GaugeSetupoid: Int, type: Int, time: Int, maxTime: IntA progress bar appeared. type: 0 — cast, 1 — reuse. time and maxTime are in milliseconds.

Target

EventFieldsWhen it arrives
TargetSelectedtargetOid: IntMy character selected a target.
TargetClearedMy character cleared its target.
AnyTargetSelectedoid: Int, targetOid: IntSomeone else selected a target.
AnyTargetClearedoid: IntSomeone else cleared their target.

Own character

EventFieldsWhen it arrives
PlayerUpdatedblocks: IntCharacter data changed. blocks marks which data blocks arrived, as sent by the server.
BuffsUpdatedThe buff list changed.
SkillListUpdatedThe skill list changed.
SitStandChangedsitting: BooleanThe character sat down or stood up.
VitalityPointsUpdatedpoints: IntVitality points changed.
NevitPointsUpdatedpoints: IntNevit points changed.
NevitTimeUpdatedstarted: Boolean, timeLeftMs: IntThe Nevit timer started or updated.
EnterWorldReceivedserverEpochSec: Int, tzOffsetSec: Int, daylightSec: IntEntering the world: server time and time zone.
ManorListReceivedcastleIds: List<Int>The manor list arrived.
AgitDecoInfoReceivedresidenceId: IntResidence information arrived.
PledgeStatusUpdatedleaderId: Int, clanId: Int, crestId: Int, allyId: Int, allyCrestId: Int, largeCrestId: IntClan status changed.
AllyCrestReceivedserverId: Int, crestId: Int, data: List<Byte>The alliance crest arrived.
PrivateStoreSellTitleReceivedsellerOid: Int, title: StringA sell store title arrived.
PrivateStoreBuyTitleReceivedbuyerOid: Int, title: StringA buy store title arrived.
PrivateStoreListReceivedsellerOid: Int, ownMoney: Long, items: List<PrivateStoreEntry>The contents of a sell store arrived. Sent only in response to acting on the seller, never pushed on its own. ownMoney is your money, not the seller's.
ShortcutRegisteredtype: Int, slot: Int, id: Int, sharedReuseGroup: Int, augOpt1: Int, augOpt2: Int, visualId: IntA shortcut was registered on the bar.
ApSkillListReceivedenable: Boolean, resetSp: Long, abilityPoints: Int, usedAbilityPoints: Int, skills: List<ApSkillEntry>The ability point skill list arrived.
ServerObjectAppearedobjectId: Int, displayId: Int, name: String, x: Int, y: Int, z: IntA server-side object appeared.
DominionWarStartedobjectId: Int, territoryId: Int, disguised: BooleanA territory war started.
UnreadMailCountReceivedcount: IntThe unread mail count arrived.
TutorialListReceiveddata: List<Byte>Tutorial data arrived.
TutorialClientEventEnabledeventId: IntA tutorial event was enabled.
TutorialHtmlClosedThe tutorial window was closed.

Movement

EventFieldsWhen it arrives
MoveStartedoid: IntAn entity started moving.
MoveStoppedoid: IntAn entity stopped.
MoveTypeChangedoid: Int, running: BooleanAn entity switched between running and walking.
AnyWaitTypeChangedoid: Int, sitting: BooleanAn entity sat down or stood up.
Teleportedoid: IntAn entity teleported.

World

EventFieldsWhen it arrives
NpcAppearedoid: IntAn NPC or mob appeared.
ObjectDisappearedoid: IntAn object went out of sight.
ItemDroppedoid: Int, itemId: Int, isMy: BooleanAn item dropped. isMy — reserved for my side.
ItemPickedUpoid: IntAn item was picked up.

Inventory

EventFieldsWhen it arrives
InventoryUpdatedThe inventory changed.
InventoryLoadedtype: InventoryListTypeA full list arrived: USER, PET or QUEST.
AutoSoulShotChangeditemId: Int, enabled: BooleanAutomatic shots were turned on or off.

Pet

EventFieldsWhen it arrives
PetSpawnedoid: Int, isOwn: BooleanA pet was summoned. isOwn — it's mine.
PetDismissedoid: IntA pet was dismissed.
PetJoinedoid: IntA pet joined the party.
PetLeftoid: IntA pet left the party.

Party

EventFieldsWhen it arrives
PartyUpdatedThe party composition or state changed.
PartyMemberJoinedoid: Int, name: StringA member joined the party.
PartyInviteReceivedname: StringA party invitation arrived from name.
PartyLeftThe party was left or disbanded.
PartyBuffsUpdatedoid: IntA party member's effects changed.

Dialogs and chat

EventFieldsWhen it arrives
DialogReceivednpcOid: IntAn NPC opened a dialog. The text is in bot.dialogText.
BoardReceivedpartId: StringA community board page arrived. The text is in bot.boardText. The server splits a long page into parts, so the event fires several times in a row.
ConfirmDialogReceivedmsgId: Int, requestId: Int, sender: StringA confirmation window arrived. Answer with confirmDialog(msgId, requestId, accept).
CaptchaReceivedmsgId: Int, params: List<ConfirmDlgParam>, endTime: Int, requestId: IntA verification prompt (captcha) arrived.
ClanInviteReceivedname: StringA clan invitation arrived.
TradeRequestReceivedsenderOid: IntA trade request arrived.
ChatMessageReceivedsenderOid: Int, chatType: Int, senderName: String, message: StringA chat message. Channel codes are on the Commands page.

System

EventFieldsWhen it arrives
SystemMessagemsgId: IntA server system message.
ActionFailedskillId: Int, targetOid: Int, castingType: IntThe server rejected an action.
MovementFailedcastingType: IntThe server rejected a movement.
NpcAggroChangedoid: Int, aggro: BooleanA mob started or stopped being aggressive towards my side.
CharSelectReadyThe character selection screen is ready.

Mail, auction, castles

EventFieldsWhen it arrives
MailSentA letter was sent.
MailListReceivedThe mail list arrived.
AuctionListReceivedThe auction lot list arrived.
AuctionSellListReceivedYour own lot list arrived.
CastleInfoReceivedCastle information arrived.

Raw packets

EventFieldsWhen it arrives
PacketInhex: StringA server→client packet, exactly as it came off the wire.
PacketOuthex: StringA client→server packet — either a bot command or something the player did by hand.

The only events that are not about the game: they say what was on the wire, not what happened. They arrive only while capture is on — see Raw traffic in the command reference.

They do not travel through bot.events or waitEvent — they have their own stream, bot.packetsIn / bot.packetsOut, and that is where to read them. The split is not cosmetic: in combat there are dozens of packets a second, and on the shared stream they would crowd out the game events someone is waiting for. As a bonus that stream hands you a ready Packet with opcode, bytes and a direction instead of a bare hex string.

Supporting types

InventoryListType

The list type in the InventoryLoaded event: USER · PET · QUEST.

ConfirmDlgParam

A parameter in the CaptchaReceived event. A sealed type with three variants:

VariantFieldsDescription
Textvalue: StringA text parameter.
Numvalue: IntA numeric parameter.
Unknowntype: IntAn unrecognised parameter; only its type is known.
Kotlin
val texts = captcha.params.filterIsInstance<ConfirmDlgParam.Text>().map { it.value }

ApSkillEntry

An entry in the ApSkillListReceived event.

FieldTypeDescription
skillIdIntSkill id.
levelIntLevel.

PrivateStoreEntry

One lot in the PrivateStoreListReceived event.

FieldTypeDescription
objectIdIntObject id of this particular item instance.
itemIdIntItem template id.
countLongHow many are on sale.
priceLongWhat the seller asks per unit.
basePriceLongThe item's reference price, so price - basePrice is how far the offer sits from par.
bodyPartIntEquipment slot mask, 0 for items that are not worn.