Brodgar client

gob:materials: The Materials an Object Is Drawn In

Read which materials a game object is drawn in, dress a slot in another, and hand it back. Unprotected: a write is client-local and visual, and nothing goes on the wire or to disk.

local session = hafen.session():current()
local cupboard = session and session:world():gob():nearest("terobjs/cupboard")
if cupboard then
  local slots = cupboard:materials()
  for _, slot in ipairs(slots:list()) do
    hafen.log():write(("slot %d: %s"):format(slot:index(), slot:native():name()))
  end
  -- Preview the first slot in the last one's material: the swap lands once the client holds the resource.
  slots:get(1):material(slots:get(slots:count()):native():name())
end

Rule Detail
Variable materials A cupboard, a chest, a cart, a boat or a wall is one model drawn in variable materials. The server sends one material resource per slot. The client wraps each part of the model tagged with that slot in it. gob:materials() is the collection of those slots.
A view Derived from the object on every call, holding nothing between them: it cannot outlive the object and needs no clean-up. An object with no variable materials (a tree, a boulder, a player or animal) counts 0. A gone object lists nothing.
Client-local A write dresses the model on this client and nowhere else, and touches nothing on disk.

The slots

gob:materials() is the collection of the object’s material slots, in slot order, keyed by 1-based position.

Method Returns Permission Description
gob:materials():list(filter) MaterialSlot[] Unprotected Every slot the server sent, in slot order.
gob:materials():count(filter) number Unprotected How many.
gob:materials():find(filter) MaterialSlot \| nil Unprotected The first that matches.
gob:materials():get(n) MaterialSlot \| nil Unprotected The slot at 1-based position n. nil past the count. 0 raises naming the 1-based rule. A fractional or non-number n raises.
gob:materials():release() the collection Unprotected Hands every slot your addon dressed on the object back to the server’s material (the endings).
Rule Detail
filter The canonical filter. A string matches the name of the resource in force on a slot as a substring. A function is called with the MaterialSlot.
Interned On object and position: gob:materials():list()[n] == gob:materials():get(n), so == compares handles and a slot works as a table key.

The MaterialSlot object

Method Returns Permission Description
slot:index() number Unprotected The 1-based position, the number :get(n) takes.
slot:wire() number Unprotected The server’s own number for the slot, index - 1: the vm tag on the model’s part.
slot:native() Resource \| nil Unprotected The material the server dressed the slot in, as a Resource handle.
slot:material() Resource \| nil Unprotected The material in force on the slot: yours once written, else the server’s.
slot:material(name [, id]) the slot Unprotected Dresses the slot in the mat2 layer of the resource named name. id is a whole number naming which layer, the resource’s first by default (the write).
slot:release() the slot Unprotected Hands the slot back to the server’s material (the endings).
slot:drawn() Resource \| nil Unprotected The material the model is drawn with right now, on the copy this handle reads through.
slot:info() table \| nil Unprotected A plain-table snapshot, {index, wire, native, material, drawn, id}. The resource reads are names. id is the layer number of the material in force within its resource, absent while a write that named none is still loading.
Rule Detail
Live A slot reads through the object every call. The resource reads and :info() answer nil once the object is gone or the server has re-sent it with fewer slots. :index() and :wire() always answer.
One handle per name slot:native(), slot:material() and slot:drawn() hand back the interned Resource for the name, so slot:material() == slot:native() compares handles. The server dressed the slot, so slot:native():loaded() is true on every slot the object has. Until you write a slot, all of them read the server’s material.

The write

slot:material(name [, id]) takes a resource name, the string resource:name() answers under hafen.resource’s rule for a well-formed name, and dresses the slot in that resource’s mat2 layer. The write is accepted at once and hands the slot back. slot:material() reads the written name from that moment. The swap lands on the next frame when the client holds the resource. The client fetches one it does not hold. slot:drawn() says which material the model is drawn with meanwhile.

The client’s state material():loaded() material():error() drawn()
Holds the resource true nil the written material, from the next frame
Still fetching it false nil the server’s material, until the fetch lands
The fetch failed (a name the server has not got) false the client’s message the server’s material
Loaded, but no mat2 layer at id true nil the server’s material
Rule Detail
Last write wins per slot Across addons: a second slot:material(name) replaces the first, on every copy.
The write lands on the object, not on one copy Every live session’s copy of the object is dressed. A session that loads the object later dresses its copy on arrival. The object’s own re-sent dressing replaces nothing of yours. How it ends is below.
gob:info().materials The names in force per slot, so a written slot reads your name there.

A write is checked when made and refused naming the rule:

Call Refused because
slot:material(nil) An explicit nil names no material. Handing a slot back to the server’s material is slot:release().
slot:material(42) The name is a string.
slot:material("gfx//x") The name is malformed (an empty segment, a .. segment, a leading /).
slot:material(name, 1.5) id is a whole number.

The endings

slot:release() hands the slot back to the server’s material. gob:materials():release() does it for every slot your addon dressed on the object. Both drop the write on every live copy and from what a later session’s copy is dressed with on arrival. slot:material() reads slot:native() again at once. slot:drawn() follows on the next frame. Both are inert, and still hand the receiver back, on a slot that is not yours: never written, released already, or dressed by another addon. That addon’s write is its own to release.

Ending What it releases
slot:release() Your write on this one slot.
gob:materials():release() Your every write on this object.
Your addon reloads or unloads Your every write on every object, in every session: the rule every visual override keeps.
The object leaves its last session Every addon’s writes on it: an object that unloads and streams in again is a new object, dressed by the server.
local session = hafen.session():current()
local chest = session and session:world():gob():nearest("terobjs/chest")
if chest and chest:materials():count() > 1 then
  local slots = chest:materials()
  -- Preview the first slot in the last one's material, then hand the whole object back.
  slots:get(1):material(slots:get(slots:count()):native():name())
  hafen.timer():after(5, function() slots:release() end)
end

See Also