Lewati ke konten utama

Events

Subscribe ke event lifecycle + paket lewat client:on(eventName, handler). Dispatcher task di-spawn lazy pas registration pertama buat satu bot, dan satu registry dipake bareng-bareng sama setiap handle getClient().

⚠️ Handler jalan di event loop runtime — jaga ringan dan async-safe. Kerjaan berat (network, sleep multi-detik) bakal block setiap event lain buat bot itu. Push kerjaan lambat ke runThread aja.


API

client:on(event, callback)

FieldType
Signature(self: Client, event: string, callback: (any) -> ()) → ()
Returnsgak ada — runtime nahan callback-nya tetep hidup di registry per-bot sampai script-nya berakhir
Asyncno — registration-nya sync; dispatcher fire callback secara async
local client = getClient()

client:on(eventName, function(...)
-- body handler
end)

List lengkap nama event yang didukung registry ada di src/seraph/events.rs — apapun di luar tabel di bawah bakal teregister diem-diem tapi gak pernah fire.


Event lifecycle

Edge-detected dari snapshot bot di cadence 50 ms. "connect" fire di tick pertama saat status nyampe MENU_READY atau IN_WORLD; "disconnect" fire di tick pertama yang keluar dari connected set.

EventKapanArgumen handler
"connect"Status nyampe MENU_READY atau IN_WORLD (TCP up + auth selesai)tidak ada
"disconnect"Status keluar dari connected set (alasan apapun)tidak ada
--!strict
local client = getClient()
if not client then return end

client:on("connect", function()
print("auth done, sitting at menu or in world")
end)

client:on("disconnect", function()
print("dropped")
end)

Event paket

Hook paket BSON incoming mentah. Dua bentuk — keduanya bisa aktif di paket yang sama berbarengan.

PatternMatchArgumen handler
"p"semua paket inbounddoc: table
"p:<ID>"cuma paket yang field ID-nya match (mis. "p:WCM", "p:OoIP")doc: table

doc itu envelope BSON yang udah di-decode jadi Lua table — key-nya mirror nama field wire. Id paket nempel di doc.ID (listener catch-all baca dari situ).

--!strict
local client = getClient()
if not client then return end

-- Watch tiap broadcast chat (BGM = "broadcast game message")
client:on("p:BGM", function(doc)
local msg = doc.CmB and doc.CmB.message or "<no message>"
local from = doc.CmB and doc.CmB.nick or "<unknown>"
print(("[chat] %s: %s"):format(from, msg))
end)

-- Catch tiap redirect server
client:on("p:OoIP", function(doc)
print(("server redirected → %s (ER=%s)"):format(doc.IP or "?", doc.ER or ""))
end)

-- Inspect stream catch-all (id ada di doc.ID)
client:on("p", function(doc)
print("packet:", doc.ID)
end)

Field BSON Binary (mis. blob AI spawn / WClSD) masuk sebagai string hex, bukan array byte mentah — decode pake bit32 atau parser hex sendiri kalau perlu.


Outbound hook — "presend"

Fire secara sinkron di dalam scheduler tick tepat sebelum outbound batch di-flush. Beda dari event lain (yang dispatch post-hoc di task terpisah), presend itu direct call — paket yang lo queue di dalam callback bakal nge-ride TCP write yang sama dengan batch trigger.

FieldType
Handler signature(batch: {Packet}) → ()
Argument{Packet} — array paket yang lagi di slot batch saat ini (heartbeat, movement, pending). Tiap entry tabel; batch[i].ID = packet id.
Returndiabaikan
Syncya — scheduler nungguin tiap callback selesai sebelum flush

Contoh: place + spam-hit tile tiap tick

function breakBlocks(targetBlock, maxBlocks, minHits)
client:on("presend", function(batch)
local amount = client:inventory():count(targetBlock, InventoryItemType.block)
local clientPoint = client:point()
local tilePoint = Vector2i.new(clientPoint.x - 1, clientPoint.y)

local blocks = math.min(amount, maxBlocks)
for i = 1, blocks do
client:place(tilePoint, targetBlock, InventoryItemType.block)
for j = 1, minHits do
client:send("HB", { x = tilePoint.x, y = tilePoint.y })
end
end
end)
end

-- Fire tiap slot tick (~250ms). Berat — server bisa disconnect kalau
-- nilai flood-nya agresif. Tune hati-hati.
breakBlocks(2735, 40, 4)

Penting: presend jalan tiap tick, termasuk tick heartbeat mc=0. Apa pun yang berat di callback bakal di-multiply ~4×/detik. Pake counter / time-gate di dalam callback kalau gak mau jalan tiap tick.


Cleanup

Handler tetep teregister selama runtime script bot hidup. Script short-lived (run sekali, exit) bakal auto-cleared pas shutdown — gak perlu cleanup manual. Gak ada client:off: registry-nya gak nyediain API detach per-handler hari ini, jadi re-running kode registration yang sama bakal numpuk subscriber duplikat dan setiap event fire N kali.

Buat script long-running yang re-arm tiap iterasi, register handler sekali doang di awal, di luar loop apapun:

local client = getClient()

-- Register sekali di atas script.
client:on("connect", function() print("connected") end)
client:on("p:BGM", function(doc) print("chat:", doc.CmB and doc.CmB.message) end)

-- Baru loop.
while true do
-- kerjaan utama script…
sleep(1000)
end

Lihat juga