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.
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.
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:
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.
waitEventsubscribes at the moment of the call. If an event arrives very quickly there is a race between the command andwaitEvent: the answer is already in while nobody is listening yet. For fast reactions subscribe up front — run acollectin a separate coroutine — or check the world state instead.- The buffer is finite. If your
collecthandler is slow, the oldest events are dropped. Keep heavy work out ofcollect.
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
| Event | Fields | When it arrives |
|---|---|---|
Died | oid: Int, sweepable: Boolean | An entity died. sweepable — the corpse can be swept. |
Revived | oid: Int | An entity was revived. |
SkillUsed | casterOid: Int, targetOid: Int, skillId: Int, skillLevel: Int | A skill cast started. |
SkillFailed | skillId: Int, targetOid: Int | A skill cast did not happen. |
SkillLanded | casterOid: Int, targetOids: List<Int> | A skill took effect on the listed targets. |
CastCancelled | oid: Int | A cast was interrupted. |
AttackStarted | oid: Int | An entity started attacking. |
AttackStopped | oid: Int | An entity stopped attacking. |
StatsUpdated | oid: Int | An entity's stats changed (HP, MP and so on). |
GaugeSetup | oid: Int, type: Int, time: Int, maxTime: Int | A progress bar appeared. type: 0 — cast, 1 — reuse. time and maxTime are in milliseconds. |
Target
| Event | Fields | When it arrives |
|---|---|---|
TargetSelected | targetOid: Int | My character selected a target. |
TargetCleared | — | My character cleared its target. |
AnyTargetSelected | oid: Int, targetOid: Int | Someone else selected a target. |
AnyTargetCleared | oid: Int | Someone else cleared their target. |
Own character
| Event | Fields | When it arrives |
|---|---|---|
PlayerUpdated | blocks: Int | Character data changed. blocks marks which data blocks arrived, as sent by the server. |
BuffsUpdated | — | The buff list changed. |
SkillListUpdated | — | The skill list changed. |
SitStandChanged | sitting: Boolean | The character sat down or stood up. |
VitalityPointsUpdated | points: Int | Vitality points changed. |
NevitPointsUpdated | points: Int | Nevit points changed. |
NevitTimeUpdated | started: Boolean, timeLeftMs: Int | The Nevit timer started or updated. |
EnterWorldReceived | serverEpochSec: Int, tzOffsetSec: Int, daylightSec: Int | Entering the world: server time and time zone. |
ManorListReceived | castleIds: List<Int> | The manor list arrived. |
AgitDecoInfoReceived | residenceId: Int | Residence information arrived. |
PledgeStatusUpdated | leaderId: Int, clanId: Int, crestId: Int, allyId: Int, allyCrestId: Int, largeCrestId: Int | Clan status changed. |
AllyCrestReceived | serverId: Int, crestId: Int, data: List<Byte> | The alliance crest arrived. |
PrivateStoreSellTitleReceived | sellerOid: Int, title: String | A sell store title arrived. |
PrivateStoreBuyTitleReceived | buyerOid: Int, title: String | A buy store title arrived. |
PrivateStoreListReceived | sellerOid: 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. |
ShortcutRegistered | type: Int, slot: Int, id: Int, sharedReuseGroup: Int, augOpt1: Int, augOpt2: Int, visualId: Int | A shortcut was registered on the bar. |
ApSkillListReceived | enable: Boolean, resetSp: Long, abilityPoints: Int, usedAbilityPoints: Int, skills: List<ApSkillEntry> | The ability point skill list arrived. |
ServerObjectAppeared | objectId: Int, displayId: Int, name: String, x: Int, y: Int, z: Int | A server-side object appeared. |
DominionWarStarted | objectId: Int, territoryId: Int, disguised: Boolean | A territory war started. |
UnreadMailCountReceived | count: Int | The unread mail count arrived. |
TutorialListReceived | data: List<Byte> | Tutorial data arrived. |
TutorialClientEventEnabled | eventId: Int | A tutorial event was enabled. |
TutorialHtmlClosed | — | The tutorial window was closed. |
Movement
| Event | Fields | When it arrives |
|---|---|---|
MoveStarted | oid: Int | An entity started moving. |
MoveStopped | oid: Int | An entity stopped. |
MoveTypeChanged | oid: Int, running: Boolean | An entity switched between running and walking. |
AnyWaitTypeChanged | oid: Int, sitting: Boolean | An entity sat down or stood up. |
Teleported | oid: Int | An entity teleported. |
World
| Event | Fields | When it arrives |
|---|---|---|
NpcAppeared | oid: Int | An NPC or mob appeared. |
ObjectDisappeared | oid: Int | An object went out of sight. |
ItemDropped | oid: Int, itemId: Int, isMy: Boolean | An item dropped. isMy — reserved for my side. |
ItemPickedUp | oid: Int | An item was picked up. |
Inventory
| Event | Fields | When it arrives |
|---|---|---|
InventoryUpdated | — | The inventory changed. |
InventoryLoaded | type: InventoryListType | A full list arrived: USER, PET or QUEST. |
AutoSoulShotChanged | itemId: Int, enabled: Boolean | Automatic shots were turned on or off. |
Pet
| Event | Fields | When it arrives |
|---|---|---|
PetSpawned | oid: Int, isOwn: Boolean | A pet was summoned. isOwn — it's mine. |
PetDismissed | oid: Int | A pet was dismissed. |
PetJoined | oid: Int | A pet joined the party. |
PetLeft | oid: Int | A pet left the party. |
Party
| Event | Fields | When it arrives |
|---|---|---|
PartyUpdated | — | The party composition or state changed. |
PartyMemberJoined | oid: Int, name: String | A member joined the party. |
PartyInviteReceived | name: String | A party invitation arrived from name. |
PartyLeft | — | The party was left or disbanded. |
PartyBuffsUpdated | oid: Int | A party member's effects changed. |
Dialogs and chat
| Event | Fields | When it arrives |
|---|---|---|
DialogReceived | npcOid: Int | An NPC opened a dialog. The text is in bot.dialogText. |
BoardReceived | partId: String | A 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. |
ConfirmDialogReceived | msgId: Int, requestId: Int, sender: String | A confirmation window arrived. Answer with confirmDialog(msgId, requestId, accept). |
CaptchaReceived | msgId: Int, params: List<ConfirmDlgParam>, endTime: Int, requestId: Int | A verification prompt (captcha) arrived. |
ClanInviteReceived | name: String | A clan invitation arrived. |
TradeRequestReceived | senderOid: Int | A trade request arrived. |
ChatMessageReceived | senderOid: Int, chatType: Int, senderName: String, message: String | A chat message. Channel codes are on the Commands page. |
System
| Event | Fields | When it arrives |
|---|---|---|
SystemMessage | msgId: Int | A server system message. |
ActionFailed | skillId: Int, targetOid: Int, castingType: Int | The server rejected an action. |
MovementFailed | castingType: Int | The server rejected a movement. |
NpcAggroChanged | oid: Int, aggro: Boolean | A mob started or stopped being aggressive towards my side. |
CharSelectReady | — | The character selection screen is ready. |
Mail, auction, castles
| Event | Fields | When it arrives |
|---|---|---|
MailSent | — | A letter was sent. |
MailListReceived | — | The mail list arrived. |
AuctionListReceived | — | The auction lot list arrived. |
AuctionSellListReceived | — | Your own lot list arrived. |
CastleInfoReceived | — | Castle information arrived. |
Raw packets
| Event | Fields | When it arrives |
|---|---|---|
PacketIn | hex: String | A server→client packet, exactly as it came off the wire. |
PacketOut | hex: String | A 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:
| Variant | Fields | Description |
|---|---|---|
Text | value: String | A text parameter. |
Num | value: Int | A numeric parameter. |
Unknown | type: Int | An unrecognised parameter; only its type is known. |
val texts = captcha.params.filterIsInstance<ConfirmDlgParam.Text>().map { it.value }ApSkillEntry
An entry in the ApSkillListReceived event.
| Field | Type | Description |
|---|---|---|
skillId | Int | Skill id. |
level | Int | Level. |
PrivateStoreEntry
One lot in the PrivateStoreListReceived event.
| Field | Type | Description |
|---|---|---|
objectId | Int | Object id of this particular item instance. |
itemId | Int | Item template id. |
count | Long | How many are on sale. |
price | Long | What the seller asks per unit. |
basePrice | Long | The item's reference price, so price - basePrice is how far the offer sits from par. |
bodyPart | Int | Equipment slot mask, 0 for items that are not worn. |