Skip to content

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 against templates/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.

<?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.

Besides what the schema says, the server stops at startup, with the file and the line, for:

  • a button or button_item without exactly one of on_click, id, page or open;
  • an html with both cliloc and message, or with cliloc and a text of its own;
  • a text with a cliloc and no width and height, or a text or label_cropped with a cliloc and a hue, a message or a text of its own;
  • a text, label_cropped or html with both message and a text of its own;
  • the same button id, the same switch (checkboxes and radios together) or the same entry twice in a gump;
  • a page button 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 open that names a gump that does not exist;
  • the same gump id in two files.

gump.send checks a gump built in Lua by the same rules.

${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.

A gump can lead to another, with the same arguments, as a wizard does:

  • bind="name" on a text_entry, checkbox or radio writes the answer into the argument name when the player answers: the text, true/false, or the switch of the radio that is on (false when 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).

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.

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.

-- scripts/gumps/release_pet.lua
release_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) end

response 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 gump
gump.close(player, "release_pet")
gump.is_open(player, "release_pet") -- true while the player has it open
gump.close_all(player) -- every gump of the player; gives how many were open

From 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.

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_length at 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.

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.

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

Buy me a coffee