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
runThreadaja.
API
client:on(event, callback)
| Field | Type |
|---|---|
| Signature | (self: Client, event: string, callback: (any) -> ()) → () |
| Returns | gak ada — runtime nahan callback-nya tetep hidup di registry per-bot sampai script-nya berakhir |
| Async | no — 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.
| Event | Kapan | Argumen 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.
| Pattern | Match | Argumen handler |
|---|---|---|
"p" | semua paket inbound | doc: 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 pakebit32atau 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.
| Field | Type |
|---|---|
| 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. |
| Return | diabaikan |
| Sync | ya — 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:
presendjalan 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
Client → Generic packet send—client:send(name, params)itu lawan dariclient:on("p:<ID>", …)- Threading — push kerja lambat keluar event loop pake
runThread