Gumps
Gumps are the dialogs the server opens on a player: a layout of backgrounds, texts, buttons, checkboxes and text fields, and the player’s answer coming back to the server.
A gump is two files:
templates/gumps/<id>.xml: the layout, checked againsttemplates/gumps/gump.xsd;scripts/gumps/<id>.lua: what happens when the player answers or closes it.
Parts that change with the data come from the script: a <slot> in the XML, or a whole gump
built in Lua. New to gumps? Follow Your first gump.
The layout
Section titled “The layout”<?xml version="1.0" encoding="utf-8"?><gump id="release_pet" x="100" y="100" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="gump.xsd"> <background x="0" y="0" gump="5054" width="270" height="140" /> <html x="20" y="15" width="230" height="40" cliloc="1070722" args="${pet_name}" /> <text x="20" y="60" hue="1152">Release ${pet_name}?</text> <button x="20" y="100" up="4005" down="4007" on_click="release" /> <html x="55" y="100" width="75" height="20" cliloc="1011011" /> <button x="135" y="100" up="4005" down="4007" on_click="cancel" /> <html x="170" y="100" width="75" height="20" cliloc="1011012" /></gump>With xsi:noNamespaceSchemaLocation="gump.xsd", VS Code (with an XML extension) and Rider
complete the elements and attributes and flag mistakes as you type. The server checks every file
against the same schema at startup: a mistake stops it with the file, the line and the reason.
<gump> takes id (lower case; it should match the file name, and the script is found by the
id, not by the file name), x and y, and closable,
movable, disposable and resizable (true unless set to false), and master, the id of a master gump
the frame is taken from. Its controls show on every page;
the controls inside each <page> show on pages 1, 2, … in order.
| Element | Shows |
|---|---|
background |
A resizable background (gump, width, height) |
alpha_region |
A see-through region |
image, image_tiled |
A gump image (gump, image also takes an optional hue), or repeated over a box (image_tiled, with no hue) |
item |
An item graphic (item, optional hue) |
sprite |
A gump image cut to a box (gump, width, height; source_x and source_y say where the cut starts) |
portrait |
The picture of a creature, by its body (body, optional hue); see Creature portraits |
text |
A line of text (hue, message), or a client message in a box (cliloc, args, color, with width and height) |
label_cropped |
Text cut to a box (hue, message), or a client message (cliloc, args, color) |
html |
HTML text in a box (background, scrollbar, message, optional color), or a client message (cliloc, color, args) |
button |
A button with its two images (up, down, both required): on_click (a function of the script), id (to on_button), page (turns the page) or open (opens another gump) |
button_item |
A button with the picture of an item over its images (up, down, item, hue, width, height), answering like button |
checkbox |
A checkbox with its two images (off, on, both required; switch id, checked, bind) |
group with radio |
Radio buttons of which one can be on (off, on, both required; switch, checked, bind) |
text_entry |
A text field (entry id, hue, max_length up to 239, bind, literal; its inner text is the starting text, with ${name} filled and spaces collapsed unless literal="true", which keeps a text somebody wrote as it is) |
tooltip |
The tooltip of the control before it (cliloc, args) |
item_property |
The tooltip of a real item (serial) |
slot |
Where the script adds controls when the gump opens (name, x, y); see Slots |
A text comes from one of three places:
| Source | How | Translated by |
|---|---|---|
| The element | <text ...>Hello ${name}</text> |
Nobody |
| A server message | message="30083" on text, label_cropped, html |
The server, from data/messages, in the server language |
| A client message | cliloc="1011011" on html, text, label_cropped and tooltip |
The client, in the player’s language |
A text or label_cropped with a cliloc is drawn as a box (a text needs width and height)
and takes color, a 15-bit colour, never hue. Use client messages for the standard texts the client already has (CONTINUE 1011011,
CANCEL 1011012, …) and server messages for your own texts, which the shard can translate. Both in
one gump can mix two languages until per-player languages exist.
What the server refuses
Section titled “What the server refuses”Besides what the schema says, the server stops at startup, with the file and the line, for:
- a
buttonorbutton_itemwithout exactly one ofon_click,id,pageoropen; - an
htmlwith bothclilocandmessage, or withclilocand a text of its own; - a
textwith aclilocand nowidthandheight, or atextorlabel_croppedwith aclilocand ahue, amessageor a text of its own; - a
text,label_croppedorhtmlwith bothmessageand a text of its own; - the same button
id, the sameswitch(checkboxes and radios together) or the sameentrytwice in a gump; - a
pagebutton that turns to a page the gump does not have (a placeholder page is only known when the gump opens, and one the gump lacks then makes a button that does nothing); - a
<slot>in a gump with<page>elements; - an
openthat names a gump that does not exist; - the same gump
idin two files.
gump.send checks a gump built in Lua by the same rules.
Placeholders
Section titled “Placeholders”${name} in a text or a number is filled from the arguments the gump is opened with; a missing
one is empty. The ids switch and entry, and max_length, take plain numbers only, so
the schema can check them; the id and page of a button take a placeholder too (a button id that is not a
number from 1, or that another button has, is not drawn, with a warning in the log). args (tab separated) fills a client message’s ~1_NAME~ markers. @, { and }
are removed from client message arguments, so a player’s name cannot break the layout.
Gumps in a row
Section titled “Gumps in a row”A gump can lead to another, with the same arguments, as a wizard does:
bind="name"on atext_entry,checkboxorradiowrites the answer into the argumentnamewhen the player answers: the text,true/false, or theswitchof the radio that is on (falsewhen none of its group is);open="other_gump"on a button opens that gump with the arguments, bound values included.
<text_entry x="20" y="40" width="200" height="20" entry="1" bind="name">${name}</text_entry><button x="20" y="80" up="4005" down="4007" open="step2" />step2 can show ${name}, and every callback of either gump finds args.name. The server
checks at startup that the gump an open names exists. A script decides by itself where to go with
gump.open(player, "step2", args).
Creature portraits
Section titled “Creature portraits”A gump cannot draw a mobile. <portrait x="20" y="20" body="${body}" /> draws the art of an item that
looks like the creature, the one data/portraits.toml names for that body; a body
without one leaves the place empty, with a warning in the log. gump.portrait(body) gives that item
id to a script, and g:portrait{ x = 20, y = 20, body = 226 } adds the picture to a built gump.
<slot name="rows" x="20" y="45" /> (plain numbers, no placeholders) is filled when the gump opens: the server calls the function
rows of the gump’s script with a builder, rows(g, player, args), and puts what it adds at the
slot, coordinates counted from the slot. Pages the function makes (g:page(), g:paginate) are
added as the gump’s pages, so a slot cannot be in a gump with <page> elements: the server refuses
it at startup. A missing slot function leaves the slot empty with a warning; one that fails or calls
wait() keeps the gump from opening. What the slot adds is checked like a gump file. Only
gump.open from a script fills the slots: a gump opened from C# keeps them empty, with
a warning in the log.
Gumps built in Lua
Section titled “Gumps built in Lua”gump.create(id, x, y) gives a builder whose methods add the controls of the XML elements of the
same name, with the same attributes; gump.send(player, g, args) opens it:
local g = gump.create("pet_list", 100, 100)g:background{ x = 0, y = 0, gump = 9200, width = 300, height = 300 }g:text{ x = 20, y = 15, hue = 1152, text = "Your pets" }g:pager{ previous = { x = 20, y = 260 }, next = { x = 250, y = 260 } }
for i, pet in ipairs(pets) do local row = g:paginate(i, 10) g:button{ x = 20, y = 45 + row * 22, up = 4005, down = 4007, on_click = function(player, response, args) npc.say(pet.serial, "*follows*") end } g:text{ x = 55, y = 45 + row * 22, text = pet.name }end
gump.send(player, g, {})| Method | Adds |
|---|---|
g:background{}, g:alpha_region{}, g:image{}, g:image_tiled{}, g:item{}, g:sprite{}, g:portrait{} |
The element of the same name |
g:text{}, g:label_cropped{}, g:html{}, g:text_entry{} |
The element, text = "..." being its text |
g:button{}, g:button_item{}, g:checkbox{}, g:radio{}, g:tooltip{}, g:item_property{} |
The element; on_click may be a function |
g:group() |
A radio group: the radios after it belong to it |
g:page() |
A new page: what follows shows on it |
g:pager{ previous = {...}, next = {...} } |
Where g:paginate puts its buttons (x, y, up, down); call it before g:paginate |
g:paginate(index, per_page, columns) |
A new page every per_page items, with the buttons between pages; returns the item’s row on its page, from 0, and with columns the column too |
g:paginate reads the pager when it runs, so g:pager comes first. Without it, or for a value
it leaves out, the next button is at (260, 340) with the images 4005 and 4007, and the previous
one at (20, 340) with 4014 and 4015.
Every method but g:paginate returns g, so calls can be chained. A button whose on_click is a
function calls it with (player, response, args); the function sees the variables around it, such
as pet above, and belongs with the script that sent the gump. gump.send checks the gump as a
file is checked and fails with the reason, such as a checked that is not true or false. A built
gump answers like an XML one: texts, bind, open and on_close are the same. Placeholders are
escaped in html; text you join yourself, such as "Hi " .. name, is not, so write
text = "Hi ${name}" and pass name in the arguments. Escaping covers &, <, > and ", so a
placeholder inside a quoted attribute, such as href="${link}", cannot break out of it.
on_click names starting with __ are reserved for the server.
The script
Section titled “The script”-- scripts/gumps/release_pet.luarelease_pet = {}
-- An on_click button: the player, the answer and the arguments the gump was opened with.function release_pet.release(player, response, args) npc.say(args.pet, "*leaves*")end
function release_pet.cancel(player, response, args) end
-- Buttons with an id instead of on_click.function release_pet.on_button(player, button, response, args) end
-- The gump went away without a button: "player" (closed by the player), "replaced" (opened again),-- "server" (gump.close, or too many gumps open) or "disconnect".function release_pet.on_close(player, args, reason) endresponse is { button = n, switches = { [id] = true }, text = { [id] = "..." } }: the switches that
are on and the text of each text entry, already checked by the server.
Open, close and ask about a gump from any script:
gump.open(player, "release_pet", { pet_name = "Fido", pet = serial }) -- false for an unknown gumpgump.close(player, "release_pet")gump.is_open(player, "release_pet") -- true while the player has it opengump.close_all(player) -- every gump of the player; gives how many were openFrom a script, gump.open, gump.send, gump.close and gump.close_all do their work on the next turn of
the game loop, so a true means queued: a gump that then fails to open is only in the log. close_all gives how many
were open when it was called.
In game, .gump release_pet pet_name=Fido opens it on yourself to try it.
The arguments take strings, numbers and booleans, and come back to every callback.
Safety
Section titled “Safety”The server keeps every gump it opens on each player, and checks every answer:
- an answer for a gump the player was not sent, or already answered, is dropped;
- an answer with a button, switch or text entry the gump does not have, or the same text entry twice, is dropped;
- a text longer than 239 characters is dropped (the schema keeps
max_lengthat 239 or less); - opening a gump with the same id closes the one already open, and a player keeps at most 64 gumps.
A forged answer never reaches a script. Clients from 5.0.0a get the compressed packet (0xDD), older ones the plain one (0xB0); see the packet reference.
From C#
Section titled “From C#”A plugin opens the gumps of templates/gumps with IGumpTemplateService:
// On the game loop, with a callback:templates.Open(session, "release_pet", new Dictionary<string, string> { ["pet_name"] = "Fido" }, (session, answer) => { /* answer.Click is the on_click name, answer.Response the answer */ });
// Off the loop, waiting for the on_click name (null when closed, or for an id button):var choice = await templates.AskAsync(session, "decorate_confirm", new Dictionary<string, string>());.decorate asks this way with templates/gumps/decorate_confirm.xml; the other shipped gump is
templates/gumps/go.xml, the travel menu of .go, templates/gumps/jail_sentence.xml,
the gump of .jail, and templates/gumps/gmtools.xml, the tools of
.gmtools: a gump with two slots, a sidebar and a panel, and templates/gumps/items.xml, the item
search of .items: a gump with one slot that holds a search box, a list with pages and the
buttons that give the item, and templates/gumps/npcs.xml, the NPC search of .npcs, built the
same way with a picture of the body and a button that spawns.
The folder ships twenty-two gumps in all. The others are craft_menu.xml (the crafting menu), guild_menu.xml (the guild of a
guildstone), help_menu.xml, help_page_kind.xml, pages.xml and pages_detail.xml (the help menu and the GM
page queue), pet_release.xml, stable_claim.xml, report_murder.xml, resurrect.xml, polymorph_forms.xml (the forms
Polymorph offers) and the three tutorial_*.xml (greeting, list, name).
The answer also carries answer.Open, the gump an open button names (null for any other
button), and answer.Bound, the values of the controls with bind by name. AskAsync follows
open buttons by itself, adding the bound values to the arguments; with Open the callback has
to open the next gump. Open returns false when the session is gone. A <slot> stays empty in
a gump opened from C#: only gump.open from a script calls the slot functions.
A layout built in code goes through IGumpService, with one entry per command:
var layout = new GumpLayout() .Add(new GumpBackground { X = 0, Y = 0, GumpId = 5054, Width = 270, Height = 120 }) .Add(new GumpButton { X = 20, Y = 80, Up = 4005, Down = 4007, ButtonId = 1 });
gumps.Open(session, new GumpInstance{ Id = "my_gump", Layout = layout, X = 100, Y = 100, OnResponse = (session, response) => { /* response.ButtonId, Switches, Texts */ }, OnClosed = (session, reason) => { /* replaced, server or disconnect */ }});The entries are GumpPage, GumpGroup, GumpBackground, GumpAlphaRegion, GumpImage,
GumpImageTiled, GumpItem, GumpButton, GumpCheckbox, GumpRadio, GumpText,
GumpLabelCropped, GumpHtml, GumpHtmlLocalized, GumpTextEntry, GumpTooltip,
GumpItemProperty and GumpFlag.
Readable letters
Section titled “Readable letters”The document parchment shows saved plain text with a scrollable body. An eligible letter in the reader’s backpack adds a localized Claim attachments button. The footer reduces the body height to keep it inside the parchment. Closing is read-only. Responses follow the same serial, type and offered-button validation as other gumps; ownership and the original session are checked again before claiming. A successful claim reopens the same text without the button; a capacity refusal reopens it for a retry.
Made with ❤️ by Tom
