hafen.event: The World
A game object coming into view and leaving it. What the game or your addon attaches to one. A click on an entity you put in the world. None carries a session: the world is the client’s, so each fires once however many of your characters are looking. Part of the catalogue.
hafen.event():on("GobAdded", function(gob)
local name = gob:name()
if name and name:find("trees/", 1, true) then
gob:overlay():add("tree"):text("tree"):color{200, 210, 220} -- labelled from the first frame
end
end)
World
| Event |
Payload |
Fires |
GobAdded |
Gob |
A game object enters the view of the first of your characters to see it. |
GobRemoved |
Gob |
It leaves the view of the last one that could. |
GobOverlayAdded |
event: :gob() :key() :native() |
Something is attached to a game object (gob:overlay()). |
GobOverlayRemoved |
event: :gob() :key() :native() |
Something attached to a game object goes away. |
GobSdtChanged |
event: :gob() :sdt() |
The state bytes on a resource-drawn object change (gob:sdt()). |
GobOverlaySdtChanged |
event: :gob() :key() :sdt() |
The state bytes of the game’s overlays under one key change (overlay:sdt()). |
| Rule |
Detail |
| Instead of scanning |
Prefer these over session:world():gob():list every frame. event:gob() is a live Gob. |
GobRemoved is already gone |
Only gob:id() answers there. Index the name on GobAdded. |
| One object, one event |
Five characters in front of one tree produce one GobAdded. A character walking away from an object another still sees fires nothing. gob:sessions() reads who sees it now. A session ending is its objects leaving their last view, so what only that character saw is reported gone. The overlay events follow the same rule one level down. |
Before the first drawn frame
GobAdded runs before the object it announces is drawn. The client holds a newly arrived object out of the scene until every handler has seen it. A size or an overlay written in the handler is in force on the object’s first drawn frame.
| Rule |
Detail |
| The hold changes no data |
What answers inside the handler is what answers a step later: gob:name(), gob:sdt(), gob:hitbox() for a resource-drawn object. What has not resolved answers nil. A composed object (a character or an animal carrying equipment) answers nil a moment longer and the client does not wait for it. Ask again from a timer. |
| Only while somebody is listening |
Until an addon subscribes to GobAdded nothing is held. The cost begins with the first subscription and ends at the next :reload, not at subscription:off(). The seam stays armed. A hold with nobody to hand the object to is released by the next frame. GobOverlayAdded, GobSdtChanged and GobOverlaySdtChanged arm seams of their own. |
| Drawn anyway after a second |
A handler that takes longer, or an addon disabled mid-flight, costs a late frame and never a missing object. |
| Only objects the game sends |
A thing of your own (a ghost, a sprite, a model, a standing widget, a patch) fires no GobAdded. It is drawn the moment you place it. Neither does a gob the client makes for itself to carry a footstep’s sound or a passing effect, and nothing on one fires an overlay event. |
GobRemoved holds nothing |
It reports an object that has already left. |
Overlays coming and going
GobOverlayAdded and GobOverlayRemoved cover both halves of what gob:overlay() reads.
event |
Returns |
Permission |
Description |
event:gob() |
Gob |
Unprotected |
The gob the overlay is attached to. |
event:key() |
string |
Unprotected |
The overlay’s key. A resource name for a native one. |
event:native() |
boolean |
Unprotected |
false for one you attached, true for one the game put there (a lit fire’s flame). |
hafen.event():on("GobOverlayAdded", function(event)
if event:native() then hafen.log():write(event:gob():id() .. " now carries " .. event:key()) end
end)
| Rule |
Detail |
| Yours are private, the game’s are public |
A native = false event goes only to the addon that attached it. Native events broadcast, since a resource name means the same to everyone. |
| Next frame |
Not inside the :add itself: the game’s overlays arrive on loader threads, and both halves use one moment. A handler runs on the step and reads the truth: already there on an add, already gone on a removal. |
| Re-attaching under the same key |
Fires both, the removal then the add. |
| Counted by key |
Several game overlays may share one resource and collapse to one key, so a second of that resource arriving is not an add. overlay:count() has the multiplicity, and GobOverlaySdtChanged fires when it moves. |
| When a gob leaves |
Yours on it are reported gone before its GobRemoved. The game’s are not: the client drops a departing gob whole, and a native removal is reported only while the gob is there. A :reload fires neither. |
The state changing
GobSdtChanged fires when gob:sdt() changes: a crop advancing, a gate swinging open, a stockpile’s count moving.
event |
Returns |
Permission |
Description |
event:gob() |
Gob |
Unprotected |
The gob whose state changed. |
event:sdt() |
number[] |
Unprotected |
The new bytes, the shape gob:sdt() answers. |
hafen.event():on("GobSdtChanged", function(event)
hafen.log():write(event:gob():id() .. " now carries " .. #event:sdt() .. " state byte(s)")
end)
| Rule |
Detail |
| Once per object |
The rule of GobAdded/GobRemoved. Fires for the first state a gob is given as well, so no handler polls gob:sdt() while a resource loads. A gob whose resource carries no state never fires. |
| Next frame |
Like the overlay events, and after the GobAdded that introduced the object when both land in one frame. |
event:sdt() is that firing’s own bytes |
Never a fresh gob:sdt() read: two changes in one frame would otherwise both answer the last one. |
| Never owner-scoped |
The state belongs to the server’s object. Every subscriber is told alike. |
An overlay’s state changing
GobOverlaySdtChanged fires when what overlay:sdt() answers for one of the game’s overlays changes while its key is on the gob: a second overlay of one resource arriving, one of two leaving, a member’s bytes rewritten. A cave-in warning’s dust settling on a gob that already carries some is heard here and nowhere else.
event |
Returns |
Permission |
Description |
event:gob() |
Gob |
Unprotected |
The gob the overlays hang on. |
event:key() |
string |
Unprotected |
The overlays’ resource name, the key gob:overlay() reads them under. |
event:sdt() |
number[][] |
Unprotected |
The new value, the shape overlay:sdt() answers: one array of 0..255 numbers per member, in arrival order. |
hafen.event():on("GobOverlaySdtChanged", function(event)
local members = event:sdt()
local newest = members[#members]
hafen.log():write(("%s on %d: %d member(s), newest first byte %s")
:format(event:key(), event:gob():id(), #members, tostring(newest[1])))
end)
| Rule |
Detail |
| Once per object |
The rule of GobSdtChanged, one level down: five characters watching one dust burst produce one event. |
| Next frame |
Like the overlay events, and after the GobOverlayAdded that introduced the key when both land in one frame. |
| Fires for the first value |
A key’s first value fires too, so a handler hears every overlay it can read bytes from. Never on the last member leaving: that moment is GobOverlayRemoved. |
event:sdt() is that firing’s own value |
Never a fresh overlay:sdt() read: two changes in one frame would otherwise both answer the last one. |
| A rewritten member can pass through two moments |
Its new copy can arrive before the old one leaves, so the event fires once holding both and once holding the new one alone. That is what overlay:count() answers at each moment. |
| Native only |
Your own overlays carry no state bytes and never fire it. |
| Never owner-scoped |
A resource name means the same to every addon. Every subscriber is told alike. |
World ghosts and sprites
| Event |
Payload |
Fires |
GhostClicked |
event: :ghost() :button() :x() :y() |
A clickable ghost of your addon is clicked. |
SpriteClicked |
event: :sprite() :button() :x() :y() |
A clickable sprite of your addon is clicked. |
ObjectClicked |
event: :object() :button() :x() :y() |
A clickable glTF object of your addon is clicked. |
PatchClicked |
event: :patch() :button() :x() :y() |
A clickable patch of your addon is clicked. |
event |
Returns |
Permission |
Description |
event:ghost() / :sprite() / :object() / :patch() |
the entity |
Unprotected |
The clicked entity. Only the one matching the event reads non-nil. |
event:button() |
number |
Unprotected |
1 left, 3 right. |
event:x(), event:y() |
number |
Unprotected |
The world point the click resolved to. |
| Rule |
Detail |
| Owner-scoped |
Fires only to the addon that owns the clicked entity. A thing you put in the world is private, and its handle never leaves your addon. |
| Consumed |
No server click, no character walk. |
| Clickable only |
A non-clickable entity is click-through and silent. A sprite facing "screen" has no world mesh and is never picked. |
| A patch is hit-tested, not picked |
Against each of the pieces it is the union of. It answers whether or not you can see that ground (behind a hill, under a house), wherever any piece is under the pointer. The others are found by the client’s pick pass and answer only where visible. |
See Also