Brodgar client

hafen.ui: The Items the Client Draws

An item is a thing the client draws, found through the icon drawing it: widget:item() on the icon, widget:items() on the widget around it. A backpack’s cells, the cursor, a recipe’s slots and the food icon on a constipation row all hand the same Item object.

local session = hafen.session():current()
for _, item in ipairs(session:ui():inventory():items():list()) do
  hafen.log():write((item:name() or item:res() or "?") .. " x" .. (item:quantity() or 1))
end

local hand = session:player():hand()                  -- the cursor, or nil while it is empty
local carried = hand and hand:item()

A container belongs to one character and is reached through that character’s session. A session nobody is looking at answers as well as the one on screen. Reading is unprotected.


Read Methods

Method Returns Permission Description
widget:items() collection of Item Unprotected Everything the widget draws, in draw order, searched deep. A window answers for the grid inside it, a crafting window for its recipe’s input and output slots. :list() is the array. Empty, never nil, for a widget drawing nothing or a stale one.
widget:item() Item \| nil Unprotected The item one icon draws. The icon is a container’s cell, the cursor, a recipe slot or a constipation row’s food icon. Or it is a listing a resource ships its own widget for. nil on every other widget. The item role is the same test.
session:player():hand():item() Item \| nil Unprotected The item on that character’s cursor.
Rule Detail
Each thing appears once The equipment window draws a worn item in every slot it fills. item:slots() names them all, so a two-slot piece of gear is one entry. A slot with no display name is listed by its identifier. An empty :slots() means not worn.
Quality inputs and tools are not items The crafting window prints them as bare pictures, so :items() lists none. What a recipe asks for is s:craft().
No find It is a one-liner over :items().

The Item object

Method Returns Permission Description
item:res() string \| nil Unprotected Resource name, the item’s stable identity.
item:name() string \| nil Unprotected Display name, once the tooltip has resolved.
item:quantity() number \| nil Unprotected The count on the icon. nil for one showing none.
item:progress() number \| nil Unprotected The arc over the icon, 0..1. nil for one painting none.
item:durability() {cur, max} \| nil Unprotected The two counts its wear row prints.
item:quality() number \| nil Unprotected The quality the tooltip shows.
item:contents() Contents \| nil Unprotected What it holds. nil for an item holding nothing.
item:container() Item \| nil Unprotected The item it sits inside. nil for one in a container widget.
item:cell() {x, y} \| nil Unprotected The 1-based grid cell it sits in — a place, where a grid control’s :cellSize() is a size.
item:slots() string[] Unprotected The equipment slots it fills, by name. Empty for anything not worn.
item:handle() number \| nil Unprotected Its server widget id, the number it is addressed by on the wire. nil once gone.
item:exists() boolean Unprotected Whether the icon drawing it is still in the tree.
item:info() table Unprotected The snapshot: every read above in one table.
item:on("Changed", fn) Sub Unprotected What the item says about itself resolved or was revised.
Rule Detail
Interned == is identity. Two reads of one icon, and the icon’s :item() and the container’s :items() entry, are one object. Keyed on the thing drawn, never on :handle(), which the server reuses.
Stale, not something else An item that moved, was eaten or consumed still answers :res(), :name() and :quantity(), while :exists() is false and :cell(), :slots() and :handle() are empty. The ItemRemoved payload is a stale item, and the only thing that says what left.

A depiction that is not an item

A depiction is painted from a resource with nothing on the wire behind it. That is a recipe slot, a price on a listing, an icon resource code puts up. It is an Item reading :res(), :name(), :quality(), :durability() and :info() off the same tooltip mechanism, and it answers nothing about where it is.

Read On a depiction
:cell(), :handle(), :container(), :contents() nil: drawn where its icon is, no server widget, nothing holding it, nothing inside.
:slots() Empty.
:exists() Its icon’s: true while the widget drawing it is in the tree.
:quantity(), :progress() What the tooltip publishes, else nil.
The protected verbs Refused, naming why. Nothing is sent. Guard on :handle().
local function label(icon)
  local item = icon:item()
  if item == nil then return nil end
  local name = item:name() or item:res() or "?"
  if item:handle() == nil then return name .. " (drawn, not held)" end
  return name .. " #" .. item:handle()
end

An item arrives before it can be described

The server sends the icon first and the tooltip after. The code that reads a quality, a wear row or a contents block out of it ships in a resource that may still be loading. Until both landed, :name(), :quality(), :durability(), :contents(), :quantity() and :progress() answer nil. item:on("Changed", fn) fires when the tooltip resolves, and again whenever the server revises it.

local face = hafen.font():get("serif"):derive():size(11)

hafen.session():current():ui():on("item", "Added", function(icon)     -- every item icon, wherever drawn
  local item = icon:item()
  if item == nil then return end
  local quality_label = icon:overlay():add("q"):anchor(0.5, 1):offset(0, -1):color{255, 230, 140}:font(face)
  local function show()
    local quality = item:quality()
    if quality then quality_label:text(tostring(math.floor(quality + 0.5))) end
  end
  show()                                              -- if already known, now
  item:on("Changed", show)                            -- and the moment it is
end)
Rule Detail
One key The server resends the whole tooltip, not the field that changed. Read what you need in the handler.
The payload is the item The same object you subscribed on, so one handler serves several items.
On the step The first frame after the client builds the description, which the draw of its icon or your own read causes.
A stale item never fires Subscribing to one is legal and inert. The subscription is per item and ends with it, or with sub:off().
A recipe slot is described on arrival The client works out what a slot names while building the icon. It arrives with :name() and :quality() answering, and nothing fires behind them. Read first, subscribe second.
A change of font is not a change of item Restyling re-renders every tooltip. Nothing fires.
Arriving and leaving Are the container’s ItemAdded/ItemRemoved, not this key.

The numbers on an icon

An icon carries up to a count in the corner and an arc round the middle. Each has two sources, a server-written field and the item’s own tooltip, folded in the same order by :quantity() and :progress(). If it is on the icon, the verb answers it. An item showing neither, or with an unresolved tooltip, answers nil to both.

Read Meaning
:quantity() Usually how many this one item is: a counted item (42 seeds of Hemp) holds nothing and this is its only count. A stack draws what is inside it, agreeing with item:contents():items():count(). The item’s own code decides what the number is — gildable gear draws its gilding count, 0 included — so read it beside :res().
:progress() A fraction with no units. The client paints a wedge and can say how far round it went, for a craft in flight as readily as for wear. Not the progress bar control, which shares the name only.

Durability: the counts a wear row prints

item:durability() answers {cur, max} as the wear row states them, cur first, drawn in red once cur reaches max. nil for gear that never wears and for an unresolved tooltip. It is not the arc: :progress() is a unitless fraction, these are two absolute counts, and an item may answer both, one or neither. What one point of wear costs is the server’s. Compare the numbers against themselves on one item.

for _, item in ipairs(hafen.session():current():ui():equipment():items():list()) do
  local durability = item:durability()
  if durability then
    hafen.log():write((item:name() or "?") .. ": worn " .. durability.cur .. " of " .. durability.max)
  end
end

Write (protected)

Each verb sends exactly what the matching click sends and hands the Item back, so a run of verbs chains. Each needs its key declared in your manifest, or the group item.*, and raises naming the key otherwise.

Method Returns Permission Description
item:use([mods]) self item.use Its default right-click action: eat, open, light. mods defaults to 0: Shift 1, Ctrl 2, Alt 4, added.
item:take() self item.take Picks it up onto the cursor, or unequips a worn item. Takes no arguments. One raises.
item:drop([n]) self item.drop Drops it on the ground.
item:transfer([n]) self item.transfer Moves it to the linked container, or to your inventory.
local first = hafen.session():current():ui():inventory():items():list()[1]
if first then first:take() end
Rule Detail
n How many of a stack to move. Defaults to -1, all of it. Only use carries modifiers: on a real click the modifier selects the count, which n states directly.
Types A value that is not a number raises naming the verb and parameter. A numeric string is still a string.
A stale item Every protected verb raises and sends nothing. Re-read the container. A depiction is refused the same way.
Applying the cursor onto an item The hand’s verb, s:player():hand():use(item).

See Also