Events
El flujo de eventos completo: qué llega, cuándo llega y qué campos trae cada evento.
La referencia de la Script API está por ahora solo en inglés — el resto del sitio sigue tu idioma.
En esta página
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, storeType: Int, ownMoney: Long, items: List<PrivateStoreEntry> | The contents of a private store arrived — selling, buying or selling as one package, as told by storeType (the StoreType codes). Sent only in response to acting on the owner, never pushed on its own. ownMoney is your money, not the owner'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. In a buy store (storeType == StoreType.BUY) three of the fields read backwards — see the descriptions.
| Field | Type | Description |
|---|---|---|
objectId | Int | Object id of this particular item instance. Always 0 in a buy store: the owner does not have the item, they want it. |
itemId | Int | Item template id. |
count | Long | How many are on sale — in a buy store, how many more the owner will buy. |
price | Long | What the seller asks per unit — in a buy store, what the owner pays 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. |
enchantLevel | Int | Enchant level. Without it, lots with the same itemId cannot be told apart. |