Area 51

From Mudlet
(Redirected from Area 51 documentation)
Jump to navigation Jump to search

This page is for the development of documentation for Lua API functions that are currently being worked on. Ideally the entries here can be created in the same format as will be eventually used in Lua Functions and its sub-sites.

Note Note: Please use the Area_51/Template to add new entries in the sections below.

Note Note: Links to other functions need to include the wiki page name before the '#' character in the link identifier on the left side of the '|' divider between the identifier and the display text. e.g.

[[Manual:Lua_Functions#getCustomLines|getCustomLines()]]

rather than:

[[#getCustomLines|getCustomLines()]]

which would refer to a link within the current (in this case Area 51) section.

Note Note: The following headings reflect those present in the main Wiki area of the Lua API functions. It is suggested that new entries are added so as to maintain a sorted alphabetical order under the appropriate heading.


Basic Essential Functions

These functions are generic functions used in normal scripting. These deal with mainly everyday things, like sending stuff and echoing to the screen.

Database Functions

A collection of functions for helping deal with the database.

Date/Time Functions

A collection of functions for handling date & time.

File System Functions

A collection of functions for interacting with the file system.

Mapper Functions

A collection of functions that manipulate the mapper and its related features.

getMapInfo, PR #8963

getMapInfo()
Returns a table of all registered map info contributors mapped to their enabled/disabled state. This allows scripts to query which map info contributors (such as "Short" or "Full") are currently active.
See also: enableMapInfo(), disableMapInfo()
Mudlet VersionAvailable in Mudlet4.21+
Returns
  • A table with contributor names as keys and boolean values indicating whether each is enabled, or nil and an error message if no map is loaded.
Example
-- check which map info contributors are active
local info = getMapInfo()
for name, enabled in pairs(info) do
  print(name .. " is " .. (enabled and "enabled" or "disabled"))
end

-- check if a specific contributor is enabled
local info = getMapInfo()
if info["Short"] then
  echo("Short map info is enabled.\n")
end

setRoomBorderColor, PR #8758

setRoomBorderColor(roomID, r, g, b[, a])
Sets a custom border color for an individual room on the 2D map. When set, this color overrides the global map border color for that specific room.
See also: getRoomBorderColor(), clearRoomBorderColor(), setRoomBorderThickness()
Mudlet VersionAvailable in Mudlet4.21+
Parameters
  • roomID:
The ID of the room to set the border color for.
  • r:
The red component of the color (0-255).
  • g:
The green component of the color (0-255).
  • b:
The blue component of the color (0-255).
  • a:
(optional) The alpha/transparency component (0-255). Defaults to 255 (fully opaque).
Returns
  • true on success, or nil and an error message if no map is loaded or the room ID is invalid.
Example
-- Set room 1 to have a red border
setRoomBorderColor(1, 255, 0, 0)

-- Set room 2 to have a semi-transparent blue border
setRoomBorderColor(2, 0, 0, 255, 128)

-- Mark all rooms with environment ID 5 (it could mean "outdoors" in your map, for example) with a green border
for _, roomID in ipairs(getRooms()) do
    if getRoomEnv(roomID) == 5 then
        setRoomBorderColor(roomID, 0, 200, 0)
    end
end


setRoomUserData #PR9299

setRoomUserData(roomID, key (as a string), value (as a string))
Stores information about a room under a given key. Similar to Lua's key-value tables, except only strings may be used here. One advantage of using userdata is that it's stored within the map file itself - so sharing the map with someone else will pass on the user data. You can have as many keys as you'd like.

There are also some default variables you can change for different effects.

table of default variables for setRoomUserData()
Variable Effect
room.ui_showName toggle displaying of the room nam
room.ui_nameOffset "x y" an offset to move the text - integers separated by a space
room.ui_nameFont the name of the font to use on the name label
room.ui_nameSize the size of the font to use on the name label
room.ui_nameColor the colour to set the name label text
room.ui_borderColor the colour of the room border
room.ui_borderThickness the thickness of the room border


Returns true if successfully set.
See also: clearRoomUserData(), clearRoomUserDataItem(), getAllRoomUserData(), getRoomUserData(), searchRoomUserData()


Example
-- can use it to store room descriptions...
setRoomUserData(341, "description", "This is a plain-looking room.")

-- or whenever it's outdoors or not...
setRoomUserData(341, "outdoors", "true")

-- how how many times we visited that room
local visited = getRoomUserData(341, "visitcount")
visited = (tonumber(visited) or 0) + 1
setRoomUserData(341, "visitcount", tostring(visited))

-- can even store tables in it, using the built-in yajl.to_string function
setRoomUserData(341, "some table", yajl.to_string({name = "bub", age = 23}))
display("The denizens name is: "..yajl.to_value(getRoomUserData(341, "some table")).name)

getRoomBorderColor, PR #8758

r, g, b, a = getRoomBorderColor(roomID)
Returns the custom border color for a room, if one has been set.
See also: setRoomBorderColor(), clearRoomBorderColor()
Mudlet VersionAvailable in Mudlet4.21+
Parameters
  • roomID:
The ID of the room to get the border color for.
Returns
  • Four numbers (r, g, b, a) if a custom border color is set, or nil if the room uses the global default border color. Returns nil and an error message if no map is loaded or the room ID is invalid.
Example
-- Check if room 1 has a custom border color
local r, g, b, a = getRoomBorderColor(1)
if r then
    echo(string.format("Room 1 has custom border: RGB(%d, %d, %d) Alpha: %d\n", r, g, b, a))
else
    echo("Room 1 uses the global border color\n")
end

clearRoomBorderColor, PR #8758

clearRoomBorderColor(roomID)
Removes the custom border color from a room, causing it to use the global map border color setting.
See also: setRoomBorderColor(), getRoomBorderColor()
Mudlet VersionAvailable in Mudlet4.21+
Parameters
  • roomID:
The ID of the room to clear the custom border color from.
Returns
  • true on success, or nil and an error message if no map is loaded or the room ID is invalid.
Example
-- Reset room 1 to use the global border color
clearRoomBorderColor(1)

-- Clear custom borders from all rooms in an area
for _, roomID in ipairs(getAreaRooms(5)) do
    clearRoomBorderColor(roomID)
end

setRoomBorderStyle, PR #8985

setRoomBorderStyle(roomID, style)
Sets the border style for a specific room on the map. This allows visually distinguishing rooms with dashed or dotted borders instead of the

default solid border - useful for marking temporary, unvisited, or special rooms.

See also: setRoomBorderColor(), setRoomBorderThickness(), getRoomBorderStyle(), clearRoomBorderStyle()
Mudlet VersionAvailable in Mudlet4.21+
Parameters
  • roomID:
The ID of the room to set the border style for.
  • style:
The border style to apply. Valid values: "dashed" (or "dash line") for a dashed border, "dotted" (or "dot line") for a dotted border, or "solid" to reset to the default solid border.
Returns
  • true on success, or nil and an error message if no map is loaded or the room ID is invalid.
Example
-- set room 1234 to have a dashed border
setRoomBorderStyle(1234, "dashed")

-- set room 1234 to have a dotted border
setRoomBorderStyle(1234, "dotted")

-- reset room 1234 back to solid border
setRoomBorderStyle(1234, "solid")

getRoomBorderStyle, PR #8985

getRoomBorderStyle(roomID)
Returns the border style for a specific room on the map.
See also: setRoomBorderStyle(), clearRoomBorderStyle()
Mudlet VersionAvailable in Mudlet4.21+
Parameters
  • roomID:
The ID of the room to get the border style for.
Returns
  • "dashed" if the room has a dashed border, "dotted" if the room has a dotted border, or nil if the room has the default solid border.
Example
local style = getRoomBorderStyle(1234)
if style then
  echo(f"Room 1234 has a {style} border.\n")
else
  echo("Room 1234 has a solid (default) border.\n")
end

clearRoomBorderStyle, PR #8985

clearRoomBorderStyle(roomID)
Resets the border style for a specific room back to the default solid border.
See also: setRoomBorderStyle(), getRoomBorderStyle()
Mudlet VersionAvailable in Mudlet4.21+
Parameters
  • roomID:
The ID of the room to clear the border style for.
Returns
  • true on success, or nil and an error message if no map is loaded or the room ID is invalid.
Example
-- reset room 1234 back to solid border
clearRoomBorderStyle(1234)

setRoomBorderThickness, PR #8758

setRoomBorderThickness(roomID, thickness)
Sets a custom border thickness for an individual room on the 2D map. When set, this thickness overrides the global default for that specific room.
See also: getRoomBorderThickness(), clearRoomBorderThickness(), setRoomBorderColor()
Mudlet VersionAvailable in Mudlet4.21+
Parameters
  • roomID:
The ID of the room to set the border thickness for.
  • thickness:
The border thickness (1-10).
Returns
  • true on success, or nil and an error message if no map is loaded, the room ID is invalid, or the thickness is out of range.
Example
-- Set room 1 to have a thick border
setRoomBorderThickness(1, 5)

-- Highlight important rooms with thick red borders
local importantRooms = {1, 5, 10, 15}
for _, roomID in ipairs(importantRooms) do
    setRoomBorderColor(roomID, 255, 0, 0)
    setRoomBorderThickness(roomID, 3)
end

getRoomBorderThickness, PR #8758

thickness = getRoomBorderThickness(roomID)
Returns the custom border thickness for a room, if one has been set.
See also: setRoomBorderThickness(), clearRoomBorderThickness()
Mudlet VersionAvailable in Mudlet4.21+
Parameters
  • roomID:
The ID of the room to get the border thickness for.
Returns
  • The thickness value (1-10) if a custom thickness is set, or nil if the room uses the global default thickness. Returns nil and an error message if no map is loaded or the room ID is invalid.
Example
-- Check if room 1 has a custom border thickness
local thickness = getRoomBorderThickness(1)
if thickness then
    echo(string.format("Room 1 has custom border thickness: %d\n", thickness))
else
    echo("Room 1 uses the global border thickness\n")
end

clearRoomBorderThickness, PR #8758

clearRoomBorderThickness(roomID)
Removes the custom border thickness from a room, causing it to use the global default thickness.
See also: setRoomBorderThickness(), getRoomBorderThickness()
Mudlet VersionAvailable in Mudlet4.21+
Parameters
  • roomID:
The ID of the room to clear the custom border thickness from.
Returns
  • true on success, or nil and an error message if no map is loaded or the room ID is invalid.
Example
-- Reset room 1 to use the global border thickness
clearRoomBorderThickness(1)

-- Clear all custom border settings from a room
clearRoomBorderColor(1)
clearRoomBorderThickness(1)


mapSymbolFontInfo, PR #4038 closed

mapSymbolFontInfo()
See also: setupMapSymbolFont()

Note Note: pending, not yet available. See https://github.com/Mudlet/Mudlet/pull/4038

returns
  • either a table of information about the configuration of the font used for symbols in the (2D) map, the elements are:
  • fontName - a string of the family name of the font specified
  • onlyUseThisFont - a boolean indicating whether glyphs from just the fontName font are to be used or if there is not a glyph for the required grapheme (character) then a glyph from the most suitable different font will be substituted instead. Should this be true and the specified font does not have the required glyph then the replacement character (typically something like ) could be used instead. Note that this may not affect the use of Color Emoji glyphs that are automatically used in some OSes but that behavior does vary across the range of operating systems that Mudlet can be run on.
  • scalingFactor - a floating point number between 0.50 and 2.00 which modifies the size of the symbols somewhat though the extremes are likely to be unsatisfactory because some of the particular symbols may be too small (and be less visible at smaller zoom levels) or too large (and be clipped by the edges of the room rectangle or circle).
  • or nil and an error message on failure.
As the symbol font details are stored in the (binary) map file rather than the profile then this function will not work until a map is loaded (or initialized, by activating a map window).

moveMapLabel, PR #6014 open

moveMapLabel(areaID/Name, labeID/Text, coordX/deltaX, coordY/deltaY[, coordZ/deltaZ][, absoluteNotRelativeMove])

Re-positions a map label within an area in the 2D mapper, in a similar manner as the moveRoom() function does for rooms and their custom exit lines. When moving a label to given coordinates this is the position that the top-left corner of the label will be positioned at; since the space allocated to a particular room on the map is ± 0.5 around the integer value of its x and y coordinates this means for a label which has a size of 1.0 x 1,0 (w x h) to position it centrally in the space for a single room at the coordinates (x, y, z) it should be positioned at (x - 0.5, y + 0.5, z).

See also: getMapLabels(), getMapLabel().
Mudlet VersionAvailable in Mudlet ?.??+

Note Note: pending, not yet available. See https://github.com/Mudlet/Mudlet/pull/6014

Parameters
  • areaID/Name:
Area ID as number or AreaName as string containing the map label.
  • labelID/Text:
Label ID as number (which will be 0 or greater) or the LabelText on a text label. All labels will have a unique ID number but there may be more than one text labels with a non-empty text string; only the first matching one will be moved by this function and image labels also have no text and will match the empty string. with mo or AreaName as string containing the map label.
  • coordX/deltaX:
A floating point number for the absolute coordinate to use or the relative amount to move the label in "room coordinates" along the X-axis.
  • coordY/deltaY:
A floating point number for the absolute coordinate to use or the relative amount to move the label in "room coordinates" along the Y-axis.
  • coordZ/deltaZ:
(Optional) A floating point number for the absolute coordinate to use or the relative amount to move the label in "room coordinates" along the Z-axis, if omitted the label is not moved in the z-axis at all.
  • absoluteNotRelativeMove:
(Optional) a boolean value (defaults to false if omitted) as to whether to move the label to the absolute coordinates (true) or to move it the relative amount from its current location (false).
Returns
true on success or nil and an error message on failure, if successful it will also refresh the map display to show the result.
Example
-- move the first label in the area with the ID number of 2, three spaces to the east and four spaces to the north
moveMapLabel(0, 2, 3.0, 4.0)

-- move the first label in the area with the ID number of 2, one space to the west, note the final boolean argument is unneeded
moveMapLabel(0, 2, -1.0, 0.0, false)

-- move the second label in the area with the ID number of 2, three and a half spaces to the west, and two south **of the center of the current level it is on in the map**:
moveRoom(1, 2, -3.5, -2.0, true)

-- move the second label in the area with the ID number of 2, up three levels
moveRoom(1, 2, 0.0, 0.0, 3.0)

-- move the second label in the "Test 1" area one space to the west, note the last two arguments are unneeded
moveRoom("Test 1", 1, -1.0, 0.0, 0.0, false)

-- move the (top-left corner of the first) label with the text "Home" in the area with ID number 5 to the **center of the whole map**, note the last two arguments are required in this case:
moveRoom(5, "Home", 0.0, 0.0, 0.0, true)

-- all of the above will return the 'true'  boolean value assuming there are the indicated labels and areas

moveRoom, PR #6010 open

moveRoom(roomID, coordX/deltaX, coordY/deltaY[, coordZ/deltaZ][, absoluteNotRelativeMove])

Re-positions a room within an area, in the same manner as the "move to" context menu item for one or more rooms in the 2D mapper. Like that method this will also shift the entirety of any custom exit lines defined for the room concerned. This contrasts with the behavior of the setRoomCoordinates() which only moves the starting point of such custom exit lines so that they still emerge from the room to which they belong but otherwise remain pointing to the original place.

See also: setRoomCoordinates()
Mudlet VersionAvailable in Mudlet ?.??+

Note Note: pending, not yet available. See https://github.com/Mudlet/Mudlet/pull/6010

Parameters
  • roomID:
Room ID number to move.
  • coordX/deltaX:
The absolute coordinate or the relative amount as a number to move the room in "room coordinates" along the X-axis.
  • coordY/deltaY:
The absolute coordinate or the relative amount as a number to move the room in "room coordinates" along the Y-axis.
  • coordZ/deltaZ:
(Optional) the absolute coordinate or the relative amount as a number to move the room in "room coordinates" along the Z-axis, if omitted the room is not moved in the z-axis at all.
  • absoluteNotRelativeMove:
(Optional) a boolean value (defaults to false if omitted) as to whether to move the room to the absolute coordinates (true) or the relative amount from its current location (false).
Returns
true on success or nil and an error message on failure, if successful it will also refresh the map display to show the result.
Example
-- move the first room one space to the east and two spaces to the north
moveRoom(1, 1, 2)

-- move the first room one space to the west, note the final boolean argument is unneeded
moveRoom(1, -1, 0, false)

-- move the first room three spaces to the west, and two south **of the center of the current level it is on in the map**:
moveRoom(1, -3, -2, true)

-- move the second room up three levels
moveRoom(2, 0, 0, 3)

-- move the second room one space to the west, note the last two arguments are unneeded
moveRoom(2, -1, 0, 0, false)

-- move the second room to the **center of the whole map**, note the last two arguments are required in this case:
moveRoom(2, 0, 0, 0, true)

-- all of the above will return the 'true'  boolean value assuming there are rooms with 1 and 2 as ID numbers

setExitWeightFilter, PR #8487 open

setExitWeightFilter(callback)
installs a custom filter that lets you adjust or block exits while Mudlet computes routes.
Use setExitWeightFilter(nil) to remove the filter and restore stored exit weights.


Note Note: setExitWeightFilter Each call clears the cached routing graph so Mudlet rebuilds it using the new logic. If your callback input is changed it might be worth setting new filter once again.


Parameters

callback one of:

  • a Lua function function(roomId, exitCommand) that Mudlet calls during pathfinding;
  • nil to clear the currently installed filter.
The callback receives:
roomId — numeric id of the room the exit begins in;
  • exitCommand — command string to take that exit (e.g. "north" or a custom command).
The callback may return:
a number to override the exit weight (lower weights are preferred);
  • false or the string "block" to prevent Mudlet from considering the exit;
  • nil to keep Mudlet’s original weight.
Returns

true on success;

nil and an error message if the pathfinding data cannot be updated (e.g. no map loaded).

Example
discourage mountain travel if your char is not dwarf, and totally avoid for elves
local race = getCharacterRaceFromFancyScript()
local mountainsEnv = 100
setExitWeightFilter(function(roomId, exitCommand)
    -- Block entirely for elves
    if race == "elf" then
       return "block" -- or false
    end
    
    -- Look up the current weight so we can base adjustments on it.
    local currentWeight = getRoomWeight(roomID) or 0
    -- Add a penalty to room for not dwarfs
    if race ~= "dwarf" and mountainsEnv == getRoomEnv(roomID) then
       return currentWeight + 25
    end
    
    -- No change: keep Mudlet's original weight.
    return nil
end)
Clearing the filter

Remove the custom logic and restore default weighting:

 setExitWeightFilter(nil)

setupMapSymbolFont, PR #4038 closed

setupMapSymbolFont(fontName[, onlyUseThisFont[, scalingFactor]])
configures the font used for symbols in the (2D) map.
See also: mapSymbolFontInfo()

Note Note: pending, not yet available. See https://github.com/Mudlet/Mudlet/pull/4038

Parameters
  • fontName one of:
  • - a string that is the family name of the font to use;
  • - the empty string "" to reset to the default {which is "Bitstream Vera Sans Mono"};
  • - a Lua nil as a placeholder to not change this parameter but still allow a following one to be modified.
  • onlyUseThisFont (optional) one of:
  • - a Lua boolean true to require Mudlet to use graphemes (character) only from the selected font. Should a requested grapheme not be included in the selected font then the font replacement character (�) might be used instead; note that under some circumstances it is possible that the OS (or Mudlet) provided color Emoji Font may still be used but that cannot be guaranteed across all OS platforms that Mudlet might be run on;
  • - a Lua boolean false to allow Mudlet to get a different glyph for a particular grapheme from the most suitable other font found in the system should there not be a glyph for it in the requested font. This is the default unless previously changed by this function or by the corresponding checkbox in the Profile Preferences window for the profile concerned;
  • - a Lua nil as a placeholder to not change this parameter but still allow the following one to be modified.
  • scalingFactor (optional): a floating point value in the range 0.5 to 2.0 (default 1.0) that can be used to tweak the rectangular space that each different room symbol is scaled to fit inside; this might be useful should the range of characters used to make the room symbols be consistently under- or over-sized.
Returns
  • true on success
  • nil and an error message on failure. As the symbol font details are stored in the (binary) map file rather than the profile then this function will not work until a map is loaded (or initialised, by activating a map window).

getMapBackgroundColor, PR #8071 open

getMapBackgroundColor()
Gets the color and transparency of the map background.

See also: setMapBackgroundColor()

Returns
  • 4 integers - red, green, blue, transparency. Colors are 0 to 255 (0 being black), and transparency is 0 to 255 (0 being completely transparent).
Example
local r, g, b, a = getMapBackgroundColor()

setMapBackgroundColor, PR #8071 open

setMapBackgroundColor(r, g, b, [transparency])
Sets the color (and optionally transparency) for the map background. Colors are 0 to 255 (0 being black), and transparency is 0 to 255 (0 being completely transparent).

See also: getMapBackgroundColor()

Parameters
  • r:
Amount of red to use, 0 (none) to 255 (full).
  • g:
Amount of green to use, 0 (none) to 255 (full).
  • b:
Amount of red to use, 0 (none) to 255 (full).
  • transparency:
(optional) amount of transparency to use, 0 (fully transparent) to 255 (fully opaque). Defaults to 255 if omitted.
Example
-- make the map have a somewhat transparent red background
setMapBackgroundColor(255,0,0,200)

-- make the map have an opaque black background
setMapBackgroundColor(0,0,0)

setMapPerspective, PR #8147 open

setMapPerspective(distance, polarAngle, azimuthalAngle)
Sets the camera's position for the 3d map. See the wiki page on spherical coordinates for more clarification on the polar and azimuthal angles.

See also: shiftMapPerspective()

Parameters
  • distance:
Distance of the camera from the focal point. 1 distance is equivalent to the distance between 10 rooms on the 3d map.
  • polarAngle:
Angle of the camera from the z-axis, e.g. 0 (looking straight down from above), 90 (looking along the x/y plane) 180 (looking straight up from below).
  • azimuthalAngle:
Angle of the camera rotating counter-clockwise from the x-axis, e.g. 0 (looking west from the east), -90 or 270 (looking north from the south) etc.
Example
-- make the map perspective looking down from above at a 45 degree angle, with north facing forward (up) on the map, 5 rooms' distance from the focus point.
setMapPerspective(0.5, 45, 270)

shiftMapPerspective, PR #8147 open

shiftMapPerspective(verticalAngle, horizontalAngle, cameraRotation)
Shifts the camera's relative position for the 3d map. All arguments are expected in degrees. Each shift of the camera occurs in the order of the arguments, i.e. first vertical shift, then horizontal, and finally the camera is rotated.

See also: setMapPerspective()

Parameters
  • verticalAngle:
How many degrees to shift the camera vertically, with positive values moving the camera up.
  • horizontalAngle:
How many degrees to shift the camera horizontally, with positive values moving the camera to the right.
  • cameraRotation:
How many degrees to rotate the camera, with positive values rotating it clockwise.
Example
-- Move the camera 10 degrees down, 20 degrees to the right, and invert the map.
shiftMapPerspective(-10, 20, 180)

Miscellaneous Functions

Miscellaneous functions.

playSpatialSound, PR #8452 open

  • playSpatialSound(settings table)

Plays spatial audio files with 3D positioning using Qt6's SpatialAudio framework. Allows precise positioning, occlusion effects, room acoustics, and environmental audio for immersive gameplay experiences.

Required Key Purpose Default Description
Yes key <unique identifier> Unique key to identify this spatial sound source for later updates or removal.
Yes name <file name> Name of the audio file. May contain directory information (i.e. ambient/forest.ogg). May be part of the profile (i.e. getMudletHomeDir().. "/spatial/wind.wav") or on the local device (i.e. "C:/Users/YourName/Documents/sound.mp3").
No url <url> Resource location where the audio file may be downloaded. Only required if file is to be downloaded remotely.
No position {azimuth, elevation, distance} {0, 0, 1} 3D position as a table: azimuth (horizontal angle in degrees, 0-360), elevation (vertical angle in degrees, -90 to 90), distance (in meters, > 0).
No volume 1 to 100 50 Volume level relative to master spatial audio volume.
No occlusion 0.0 to 1.0 0.0 Occlusion factor simulating objects blocking the sound path.
No loops -1 or >= 1 1 Number of times to loop. -1 for infinite looping.
No room {dimensions, reverb, reflection, material} Room acoustics configuration table.

See also: updateSpatialSound(), stopSpatialSound(), removeSpatialSound(), getSpatialSounds()

Mudlet VersionAvailable in Mudlet4.??+
  • Example
-- Play forest ambience behind the player
playSpatialSound({
    key = "forest_ambience",
    name = "ambient/forest_birds.ogg",
    position = {180, 0, 5}, -- behind player, 5 meters away  
    volume = 30,
    loops = -1 -- infinite loop
})

-- Play footsteps with room acoustics
playSpatialSound({
    key = "footsteps",
    name = "effects/footstep_stone.wav",
    position = {45, -10, 2}, -- front-right, slightly below, 2 meters
    volume = 60,
    room = {
        dimensions = {10, 3, 8}, -- 10m wide, 3m high, 8m deep
        reverb = 0.3,
        reflection = 0.7,
        material = "sheetrock"
    }
})

-- Download and play remote spatial sound
playSpatialSound({
    key = "wind_howl",
    name = "wind.ogg",
    url = "https://example.com/sounds/",
    position = {270, 45, 10}, -- left side, elevated, distant
    volume = 40,
    occlusion = 0.2 -- partially blocked
})

updateSpatialSound, PR #8452 open

  • updateSpatialSound(key, settings table)

Updates properties of an existing spatial audio source without stopping playback.

  • Parameters

• key: Unique identifier of the spatial sound source to update • settings table: Table containing properties to update (same format as playSpatialSound)

See also: playSpatialSound()

Mudlet VersionAvailable in Mudlet4.??+
  • Example
-- Move sound to new position
updateSpatialSound("forest_ambience", {
    position = {90, 0, 3} -- move to right side, closer
})

-- Update volume and add occlusion
updateSpatialSound("footsteps", {
    volume = 80,
    occlusion = 0.5
})

-- Update multiple properties
updateSpatialSound("wind_howl", {
    position = {315, 30, 15},
    volume = 25,
    occlusion = 0.8
})

stopSpatialSound, PR #8452 open

  • stopSpatialSound(key)

Stops playback of a spatial audio source but keeps the source available for later use.

  • Parameters

• key: Unique identifier of the spatial sound source to stop

  • Returns

• boolean: true on success, false if source not found

See also: playSpatialSound(), removeSpatialSound()

Mudlet VersionAvailable in Mudlet4.??+
  • Example
-- Stop the forest ambience
stopSpatialSound("forest_ambience")

-- Stop footsteps when player stops walking  
if not moving then
    stopSpatialSound("footsteps")
end

pauseSpatialSound, PR #8452 open

  • pauseSpatialSound(key)

Pauses playback of a spatial audio source, allowing it to be resumed later from the same position.

  • Parameters

• key: Unique identifier of the spatial sound source to pause

  • Returns

• boolean: true on success, false if source not found

See also: playSpatialSound(), stopSpatialSound()

Mudlet VersionAvailable in Mudlet4.??+
  • Example
-- Pause ambient sound temporarily
pauseSpatialSound("forest_ambience")

-- Resume by playing again (will continue from pause position)
playSpatialSound({
    key = "forest_ambience",
    name = "ambient/forest_birds.ogg",
    position = {180, 0, 5}
})

removeSpatialSound, PR #8452 open

  • removeSpatialSound(key)

Completely removes a spatial audio source, stopping playback and freeing resources.

  • Parameters

• key: Unique identifier of the spatial sound source to remove

  • Returns

• boolean: true on success, false if source not found

See also: stopSpatialSound(), getSpatialSounds()

Mudlet VersionAvailable in Mudlet4.??+
  • Example
-- Remove completed sound effect
removeSpatialSound("door_slam")

-- Clean up old ambient sounds
for _, key in ipairs({"old_wind", "old_rain", "old_birds"}) do
    removeSpatialSound(key)
end

getSpatialSounds, PR #8452 open

  • getSpatialSounds()

Returns a list of all currently active spatial audio sources.

  • Returns

• table: Indexed table containing the keys of all active spatial sound sources

See also: playSpatialSound(), removeSpatialSound()

Mudlet VersionAvailable in Mudlet4.??+
  • Example
-- List all active spatial sounds
local activeSounds = getSpatialSounds()
for i, soundKey in ipairs(activeSounds) do
    echo("Active spatial sound: " .. soundKey .. "\n")
end

-- Stop all spatial sounds
for _, soundKey in ipairs(getSpatialSounds()) do
    stopSpatialSound(soundKey)
end

-- Check if specific sound is playing
local activeSounds = getSpatialSounds()
local isPlaying = table.contains(activeSounds, "forest_ambience")

setSpatialListener, PR #8452 open

  • setSpatialListener(settings table)

Sets the position and orientation of the spatial audio listener (the player's ears).

Required Key Purpose Default Description
No position {x, y, z} {0, 0, 0} 3D position of the listener in world coordinates.
No rotation {yaw, pitch, roll} {0, 0, 0} Orientation of the listener's head: yaw (left/right), pitch (up/down), roll (tilt).

See also: playSpatialSound()

Mudlet VersionAvailable in Mudlet4.??+
  • Example
-- Set listener at origin, facing north
setSpatialListener({
    position = {0, 0, 0},
    rotation = {0, 0, 0}
})

-- Player moved to new room and is facing east  
setSpatialListener({
    position = {10, 0, 5},
    rotation = {90, 0, 0} -- 90 degrees yaw = facing east
})

-- Looking up at the sky
setSpatialListener({
    rotation = {0, 45, 0} -- 45 degrees pitch up
})

setSpatialMasterVolume, PR #8452 open

  • setSpatialMasterVolume(volume)

Sets the master volume for all spatial audio sources.

  • Parameters

• volume: Master volume level (0-100)

  • Returns

• boolean: true on success

See also: playSpatialSound()

Mudlet VersionAvailable in Mudlet4.??+
  • Example
-- Set moderate spatial audio volume
setSpatialMasterVolume(60)

-- Mute all spatial audio
setSpatialMasterVolume(0)

-- Maximum spatial audio volume
setSpatialMasterVolume(100)

playSpatialTestTone, PR #8452 open

  • playSpatialTestTone(settings table)

Plays a generated test tone at a specific spatial position for testing and calibration purposes.

Required Key Purpose Default Description
Yes key <unique identifier> Unique key to identify this test tone source.
Yes type "white", "pink", or "sine" Type of test tone to generate.
Yes duration <seconds> Duration of the test tone in seconds.
Yes azimuth <degrees> Horizontal angle (0-360 degrees).
Yes elevation <degrees> Vertical angle (-90 to 90 degrees).
Yes distance <meters> Distance from listener in meters.
No frequency <Hz> 440 Frequency for sine wave test tones.
No volume 1 to 100 50 Volume of the test tone.
No loops -1 or >= 1 1 Number of loops (-1 for infinite).

See also: playSpatialSound(), stopSpatialSound()

Mudlet VersionAvailable in Mudlet4.??+
  • Example
-- Test speaker positions with white noise
playSpatialTestTone({
    key = "test_left",
    type = "white",
    duration = 2,
    azimuth = 270,   -- left side
    elevation = 0,
    distance = 2,
    volume = 70
})

-- Test frequency response with sine wave
playSpatialTestTone({
    key = "test_1khz", 
    type = "sine",
    frequency = 1000,
    duration = 3,
    azimuth = 0,     -- front center
    elevation = 0,
    distance = 1,
    volume = 50
})

-- Test distance with pink noise
playSpatialTestTone({
    key = "test_distant",
    type = "pink", 
    duration = 5,
    azimuth = 180,   -- behind
    elevation = 0,
    distance = 10,   -- far away
    volume = 80,
    loops = 2
})

getCustomLoginTextId, PR #3952 open

getCustomLoginTextId()

Returns the Id number of the custom login text setting from the profile's preferences. Returns 0 if the option is disabled or a number greater than that for the item in the table; note it is possible if using an old saved profile in the future that the number might be higher than expected. As a design policy decision it is not permitted for a script to change the setting, this function is intended to allow a script or package to check that the setting is what it expects.

Introduced along with four other functions to enable game server log-in to be scripted with the simultaneous movement of that functionality from the Mudlet application core code to a predefined doLogin() function, a replacement for which is shown below.

See also: getCharacterName(), sendCharacterName(), sendCustomLoginText(), sendPassword().

Note Note: Not available yet. See https://github.com/Mudlet/Mudlet/pull/3952

Only one custom login text has been defined initially:

Predefined custom login texts
Id Custom text Introduced in Mudlet version
1 "connect {character name} {password}" TBD

The addition of further texts would be subject to negotiation with the Mudlet Makers.

Example
-- A replacement for the default function placed into LuaGlobal.lua to reproduce the previous behavior of the Mudlet application:
function doLogin()
  if getCustomLoginTextId() ~= 1 then
    -- We need this particular option but it is not permitted for a script to change the setting, it can only check what it is
    echo("\nUnable to login - please select the 'connect {character name} {password}` custom login option in the profile preferences.\n")
  else
    tempTime(2.0, [[sendCustomLoginText()]], 1)
  end
end

sendCharacterName, PR #3952 open

sendCharacterName()

Sends the name entered into the "Character name" field on the Connection Preferences form directly to the game server. Returns true unless there is nothing set in that entry in which case a nil and an error message will be returned instead.

Introduced along with four other functions to enable game server log-in to be scripted with the simultaneous movement of that functionality from the Mudlet application core code to a predefined doLogin() function that may be replaced for more sophisticated requirements.

See also: getCharacterName(), sendCharacterPassword(), sendCustomLoginText(), getCustomLoginTextId().

Note Note: Not available yet. See https://github.com/Mudlet/Mudlet/pull/3952

sendCharacterPassword, PR #3952 open

sendCharacterPassword()

Sends the password entered into the "Password" field on the Connection Preferences form directly to the game server. Returns true unless there is nothing set in that entry or it is too long after (or before) a connection was successfully made in which case a nil and an error message will be returned instead.

Introduced along with four other functions to enable game server log-in to be scripted with the simultaneous movement of that functionality from the Mudlet application core code to a predefined doLogin() function, reproduced below, that may be replaced for more sophisticated requirements.

See also: getCharacterName(), sendCustomLoginText(), getCustomLoginTextId(), sendCharacterName().

Note Note: Not available yet. See https://github.com/Mudlet/Mudlet/pull/3952

Example
-- The default function placed into LuaGlobal.lua to reproduce the previous behavior of the Mudlet application:
function doLogin()
  if getCharacterName() ~= "" then
    tempTime(2.0, [[sendCharacterName()]], 1)
    tempTime(3.0, [[sendCharacterPassword()]], 1)
  end
end

sendCustomLoginText, PR #3952 open

sendCustomLoginText()

Sends the custom login text (which does NOT depend on the user's choice of GUI language) selected in the preferences for this profile. The {password} (and {character name} if present) fields will be replaced with the values entered into the "Password" and "Character name" fields on the Connection Preferences form and then sent directly to the game server. Returns true unless there is nothing set in either of those entries (though only if required for the character name) or it is too long after (or before) a connection was successfully made or if the custom login feature is disabled, in which case a nil and an error message will be returned instead.

Introduced along with four other functions to enable game server log-in to be scripted with the simultaneous movement of that functionality from the Mudlet application core code to a predefined doLogin() function, a replacement for which is shown below.

See also: getCharacterName(), sendCharacterName(), sendPassword(), getCustomLoginTextId().

Note Note: Not available yet. See https://github.com/Mudlet/Mudlet/pull/3952

Only one custom login text has been defined initially:

Predefined custom login texts
Id Custom text Introduced in Mudlet version
1 "connect {character name} {password}" TBD

The addition of further texts would be subject to negotiation with the Mudlet Makers.

Example
-- A replacement for the default function placed into LuaGlobal.lua to reproduce the previous behavior of the Mudlet application:
function doLogin()
  if getCustomLoginTextId() ~= 1 then
    -- We need this particular option but it is not permitted for a script to change the setting, it can only check what it is
    echo("\nUnable to login - please select the 'connect {character name} {password}` custom login option in the profile preferences.\n")
  else
    tempTime(2.0, [[sendCustomLoginText()]], 1)
  end
end

Mudlet Object Functions

A collection of functions that manipulate Mudlet's scripting objects - triggers, aliases, and so forth.

createComposer PR #8114 open

ok = createComposer(title, text, callbackFunction)

Creates a Composer dialog window with the given title and initial text, and registers a Lua function to handle the result. When the user clicks **Save** or **Cancel**, the Composer closes and your callback function is invoked with two arguments: the resulting text and a boolean indicating whether the user saved (true) or canceled editing (false).

See also
getComposerText(), setComposerText(), getComposerTitle(), setComposerTitle()
Mudlet VersionAvailable in Mudlet4.20+
Parameters
  • title:
The title of the Composer window.
  • text:
The initial text content in the Composer. You can use \n to start a new line, \t for tabulators, etc.
  • callbackFunction:
A Lua function to call when the Composer closes. It will receive two parameters:
  • A string with the text the user entered.
  • A boolean value – `true` if the user clicked **Save**, `false` if they clicked **Cancel**.
Returns
  • Boolean `true` if the Composer was successfully created, otherwise nil + error (for example, if another Composer is already open).
Example
-- Create a Composer window for editing a note
createComposer("Edit Note", "Default text", function(editedText, isSaved)
    if isSaved then
        echo(f"Saved text: {editedText}\n")
    else
        echo("Edit was canceled.\n")
    end
end)

getComposerText PR #8114 open

text = getComposerText()

Returns the current text from the Composer window, if it is open.

See also
createComposer(), setComposerText()
Mudlet VersionAvailable in Mudlet4.20+
Parameters
  • (none)
Returns
  • The text currently displayed in the Composer, or nil + error if the Composer is not open.
Example
-- Retrieve and print the current text from the Composer
local currentText = getComposerText()
if currentText then
    echo(f"Current text in Composer: {currentText}\n")
else
    echo("No Composer is open.\n")
end


getComposerTitle PR #8114 open

title = getComposerTitle()

Returns the current title from the Composer window, if it is open.

See also
createComposer(), setComposerTitle()
Mudlet VersionAvailable in Mudlet4.20+
Parameters
  • (none)
Returns
  • The current title of the Composer, or nil + error if the Composer is not open.
Example
-- Print the Composer title
local currentTitle = getComposerTitle()
if currentTitle then
    echo(f"Composer title: {currentTitle}\n")
else
    echo("No Composer is open.\n")
end

getKeyCode PR#8435 open

keyCode, keyModifers = getKeyCode(keyID/keyName)
Returns numbers representing the key and any modifiers for a key-binding.
See also
enableKey(...), killKey(...), tempKey(...), findItems(...),

Note Note: If a name is given and it is used by more than one key then only the first one found will be considered by this function.

Note Note: Modifiers vary between different operating systems and not all of them will be present or have the names given below for all users on all systems.

Parameters
  • mandatoryParameter:
either the ID (number) e.g. as returned by the permKey(...) or tempKey(...) functions; or the "name" (string) of a key-binding.
Returns
  • Returns two values on success: the key code as used in the functions to create a key-binding; and any modifiers as a sum of the following (including as powers of two).
Modifiers table
Modifier Value (hex) Value (dec) 2n
Shift 0x02000000 33,554,432 21
Control 0x04000000 67,108,864 22
Alt 0x08000000 134,217,728 23
Keypad 0x10000000 268,435,456 24
GroupSwitch 0x20000000 536,870,912 25

The list of key codes is rather long to include here, instead view the original code in the Mudlet GitHub repository [[1]].

  • Returns two values on failure: a Lua nil and an error message,
Example
--[[Temporarily make Shift+Alt+T put something on the main console - and remember the ID for the key-binding
so it can be removed afterwards {as a temporary one it won't show up in the Editor!}]]-- 
testKey = tempKey(mudlet.keymodifier["Shift"] + mudlet.keymodifier["Alt"], mudlet.key["T"], [[echo("You pressed Shift+Alt+T didn't you!\n"]])

----Try the combination and prove that it works, then use the function to check it:
usedKey, usedModifiers = getKeyCode(testKey)
if usedKey == mudlet.T and usedModifiers == (mudlet.keymodifier.Shift + mudlet.keymodifier.Alt) then
  echo("It seems that the testKey (with ID = " .. testKey .. ") IS using the 'T' key and the 'Shift' and 'Alt' modifiers.\n")
end
----AFTER testing this, do the following to remove all the traces:
killKey(testKey)
testKey = nil
usedKey = nil
usedModifiers = nil

--Now do the same with a permanent key-binding
permKey("TestKey", "", mudlet.keymodifier["Shift"] + mudlet.keymodifier["Control"], mudlet.key["T"], [[echo("You pressed Shift+Cntl+T didn't you!\n"]])

----Try the combination and prove that it works, then use the function to check it:
usedKey, usedModifiers = getKeyCode("TestKey")
if usedKey == mudlet.T and usedModifiers == (mudlet.keymodifier.Shift + mudlet.keymodifier.Alt) then
  echo("It seems that the testKey (with name = \"TestKey\" IS using the 'T' key and the 'Shift' and 'Control' modifiers.\n")
end

----AFTER testing this, do the following to remove all the traces:
usedKey = nil
usedModifiers = nil
----AND also look for the top level "Test Key" in the "Keys" view in the editor, select it, and click the "Delete" button to remove it.

--[[Lastly, check to see if there is already a key binding for <Shift>+<Ctrl>+R
For reuse let's make it a general function - which can be copied into the code for a profile]]--
function isKeyUsed(soughtKey, soughtModifiers)
  --first get the IDs of all the current key-bindings
  local allKeys = findItems("", "keybinding", false)

  --then scan through them looking for matches for the one we want
  for _, keyID in pairs(allKeys) do
    local key, modifiers = getKeyCode(keyID)
    if key == soughtKey and modifiers == soughtModifiers then
      --got one!
      return true
    end
  end

  return false
end

----Now lets try it out - assuming that Alt+Shift+R hasn't been used so far
if isKeyUsed(mudlet.key["R"], mudlet.keymodifier["Shift"] + mudlet.keymodifier["Alt"]) then
  echo "The Alt+Shift+R is already used!"
else
  echo "The Alt+Shift+R key-binding is available..."
end

----[[Let's define it temporarily to send "rent" to the game server (and mention it on screen)
remembering the ID so we can delete the key-binding after use and then forget the ID]]----
rentKey = tempKey(mudlet.keymodifier["Shift"] + mudlet.keymodifier["Alt"], mudlet.key["R"], [[send("rent", true) killKey(]] .. rentKey .. [[) rentKey = nil]])

----Now we can check for it (before we use it!)
if isKeyUsed(mudlet.key["R"], mudlet.keymodifier["Shift"] + mudlet.keymodifier["Alt"]) then
  echo "The Alt+Shift+R is now used!"
else
  echo "The Alt+Shift+R isn't used now..."
end

----[[It is left to the reader/user to press this combination at a suitable point - either whilst connected to
a MUD to which they want to save and presumable disconnect from - or whilst disconnected to just see the
text on screen]]----

--Finally some of the error messages
----in this case an invalid keyID
local ok, err = getKeyCode(-1)
if not ok then
  debugc(f"Error: {err}\n")
  return
end

----and here providing the arguments as a table instead of an ID number or name string and a modifier:
local ok, err = getKeyCode({mudlet.key["R"], mudlet.keymodifier["Shift"] + mudlet.keymodifier["Alt"]})
if not ok then
  debugc(f"Error: {err}\n")
  return
end
Additional development notes

setComposerText PR #8114 open

setComposerText(newText)

Sets the text content of the Composer window, if it is open.

See also
createComposer(), getComposerText()
Mudlet VersionAvailable in Mudlet4.20+
Parameters
  • newText:
The text to display in the Composer. You can use \n to start a new line, \t for tabulators, etc.
Returns
  • Boolean `true` if the Title was successfully set, or nil + error if no Composer is open.
Example
-- Change the Composer text dynamically
setComposerText("Updated text for the Composer")

setComposerTitle PR #8114 open

setComposerTitle(newTitle)

Sets the title of the Composer window, if it is open.

See also
createComposer(), getComposerTitle()
Mudlet VersionAvailable in Mudlet4.20+
Parameters
  • newTitle:
The title to display in the Composer.
Returns
  • Boolean `true` if the Title was successfully set, or nil + error if no Composer is open.
Example
-- Change the Composer title dynamically
setComposerTitle("Declaration of Independence")

Networking Functions

A collection of functions for managing networking.

getTelnetOptionsStatus PR #8962, open

getTelnetOptionsStatus()
Returns the current status of telnet options that have been negotiated between Mudlet and the game server. This function is useful for debugging telnet protocol issues and understanding which features are currently enabled in your connection. The values returned are the same as those that would be reported back to the Server if it requested them via the telnet option number 5 (STATUS).
The function returns a table where each key is a telnet option number (0-255), and the value is a sub-table containing the option details. Only options that have been negotiated so far during the telnet session are included in the results. As nearly all options are handled separately in the two directions each numbered option has two statuses to report,
See also: Telnet Protocols
Mudlet VersionAvailable in Mudlet4.21+
Returns
  • A table with telnet option numbers as keys, each containing:
    • Name: A string with the human-readable name of the telnet option (e.g., "ECHO (1)", "NAWS (31)", "MCCP2 (86)")
    • Server: (optional) Boolean indicating if the server (who sent the <IAC><WILL><OPTION> to which Mudlet replied<IAC><DO><OPTION> to agree or <IAC><DONT><OPTION> to disagree to it) has this option enabled (true) or disabled (false). Only present if the server requested this option.
    • Mudlet: (optional) Boolean indicating if Mudlet (who sent the <IAC><WILL><OPTION> to which the Server replied <IAC><DO><OPTION> to agree or <IAC><DONT><OPTION> to disagree to it) has this option enabled (true) or disabled (false). Only present if Mudlet requested this option.
Example
-- Display all negotiated telnet options
local options = getTelnetOptionsStatus()

cecho("<cyan>Current Telnet Options Status:\n")
for optionNumber, details in pairs(options) do
  cecho(string.format("<white>Option %d: <yellow>%s<reset>\n", optionNumber, details.Name))
  
  if details.Server ~= nil then
    if details.Server then
      cecho("  <white>Server: <green>enabled<reset>\n")
    else
      cecho("  <white>Server: <red>disabled<reset>\n")
    end
  end
  
  if details.Mudlet ~= nil then
    if details.Mudlet ~= nil then
      cecho("  <white>Mudlet: <green>enabled<reset>\n")
    else
      cecho("  <white>Mudlet: <red>disabled<reset>\n")
    end
  end
end
-- Check if compression (MCCP2) is enabled
local options = getTelnetOptionsStatus()

-- Option 86 is MCCP2 (Mud Client Compression Protocol 2)
if options[86] and options[86].Server then
  cecho("<green>Compression is enabled!\n")
else
  cecho("<red>Compression is not active.\n")
end
-- Monitor telnet negotiation on connect
function checkTelnetFeatures()
  local options = getTelnetOptionsStatus()
  
  -- Check for common features
  local features = {
    [1] = "Echo",
    [3] = "Suppress Go Ahead", 
    [24] = "Terminal Type",
    [31] = "Negotiate Window Size (NAWS)",
    [69] = "MSDP",
    [86] = "MCCP2 Compression",
    [201] = "GMCP"
  }
  
  cecho("<cyan>Enabled Features:\n")
  for optNum, name in pairs(features) do
    if options[optNum] then
      local status = "announced"
      if options[optNum].Server and options[optNum].Mudlet then
        status = "fully enabled"
      elseif options[optNum].Server then
        status = "server enabled"
      elseif options[optNum].Mudlet then
        status = "client enabled"
      end
      cecho(string.format("<white>%s: <green>%s\n", name, status))
    end
  end
end

registerAnonymousEventHandler("sysConnectionEvent", "checkTelnetFeatures")

Note Note: This function is primarily intended for debugging telnet protocol issues. Most users won't need to use it in normal scripts, as Mudlet handles telnet negotiation automatically. At the time of introduction there is a suspicion that the status of some options were not being correctly handled and this function was created to provide a means to monitor them.

Note Note: pending, not yet available. See https://github.com/Mudlet/Mudlet/pull/8962

String Functions

These functions are used to manipulate strings.

Table Functions

These functions are used to manipulate tables. Through them you can add to tables, remove values, check if a value is present in the table, check the size of a table, and more.

Text to Speech Functions

These functions are used to create sound from written words. Check out our Text-To-Speech Manual for more detail on how this all works together.

UI Functions

These functions are used to construct custom user GUIs. They deal mainly with miniconsole/label/gauge creation and manipulation as well as displaying or formatting information on the screen.

createTextEdit, PR #8986

createTextEdit([windowName,] name, x, y, width, height)
Creates a new multi-line text editor widget. The text edit can be placed inside the main window or a user window. It supports word wrap, custom

fonts, stylesheets, placeholder text, and read-only mode. Use deleteTextEdit() to remove it.

Standard window functions (moveWindow(), resizeWindow(),

showWindow(), hideWindow(), raiseWindow(), lowerWindow(), setWindow()) all work with text edit widgets. windowType() returns "textedit" for these widgets.

See also: deleteTextEdit(), getTextEditText(), setTextEditText()
Mudlet VersionAvailable in Mudlet4.21+
Parameters
  • windowName:
(optional) The name of the parent user window. Defaults to the main window if omitted.
  • name:
The name of the text edit to create.
  • x:
The x-coordinate of the text edit.
  • y:
The y-coordinate of the text edit.
  • width:
The width of the text edit.
  • height:
The height of the text edit.
Returns
  • true on success, or raises an error if the text edit could not be created.
Example
-- create a text edit on the main window
createTextEdit("myEditor", 10, 10, 400, 200)

-- create a text edit inside a user window
createTextEdit("myUserWindow", "myEditor", 10, 10, 400, 200)

-- Geyser usage
local editor = Geyser.TextEdit:new({
name = "myEditor",
x = 10, y = 10,
width = 400, height = 200,
})

-- set some text and a placeholder
editor:setText("Hello world!")
editor:setPlaceholder("Type here...")

deleteTextEdit, PR #8986

deleteTextEdit(name)
Deletes a text edit widget. Raises the sysTextEditDeleted event with the text edit name as an

argument.

See also: createTextEdit()
Mudlet VersionAvailable in Mudlet4.21+
Parameters
  • name:
The name of the text edit to delete.
Returns
  • true on success, or false and an error message if the text edit was not found.
Example
deleteTextEdit("myEditor")

getTextEditText, PR #8986

getTextEditText(name)
Returns the current plain text content of a text edit widget.
See also: setTextEditText(), clearTextEdit()
Mudlet VersionAvailable in Mudlet4.21+
Parameters
  • name:
The name of the text edit to get the text from.
Returns
  • The text content as a string, or raises an error if the text edit was not found.
Example
local text = getTextEditText("myEditor")
echo("Editor contains: " .. text .. "\n")

-- Geyser usage
local text = myEditor:getText()

setTextEditText, PR #8986

setTextEditText(name, text)
Sets the plain text content of a text edit widget, replacing any existing content.
See also: getTextEditText(), clearTextEdit()
Mudlet VersionAvailable in Mudlet4.21+
Parameters
  • name:
The name of the text edit.
  • text:
The text to set.
Returns
  • true on success, or raises an error if the text edit was not found.
Example
setTextEditText("myEditor", "Hello world!")

-- Geyser usage
myEditor:setText("Hello world!")

clearTextEdit, PR #8986

clearTextEdit(name)
Clears all text from a text edit widget.
See also: setTextEditText(), getTextEditText()
Mudlet VersionAvailable in Mudlet4.21+
Parameters
  • name:
The name of the text edit to clear.
Returns
  • true on success, or raises an error if the text edit was not found.
Example
clearTextEdit("myEditor")

-- Geyser usage
myEditor:clear()

setTextEditReadOnly, PR #8986

setTextEditReadOnly(name, state)
Sets whether a text edit widget is read-only. When read-only, the user cannot edit the text but can still select and copy it.
See also: createTextEdit()
Mudlet VersionAvailable in Mudlet4.21+
Parameters
  • name:
The name of the text edit.
  • state:
true to make the text edit read-only, false to make it editable.
Returns
  • true on success, or raises an error if the text edit was not found.
Example
-- make it read-only to display content
setTextEditReadOnly("myEditor", true)

-- make it editable again
setTextEditReadOnly("myEditor", false)

-- Geyser usage
myEditor:setReadOnly(true)

setTextEditPlaceholder, PR #8986

setTextEditPlaceholder(name, text)
Sets the placeholder text displayed when the text edit is empty. The placeholder text is shown in a lighter color and disappears when the user

starts typing.

See also: createTextEdit()
Mudlet VersionAvailable in Mudlet4.21+
Parameters
  • name:
The name of the text edit.
  • text:
The placeholder text to display.
Returns
  • true on success, or raises an error if the text edit was not found.
Example
setTextEditPlaceholder("myEditor", "Write your message here...")

-- Geyser usage
myEditor:setPlaceholder("Write your message here...")

setTextEditStyleSheet, PR #8986

setTextEditStyleSheet(name, css)
Applies a Qt stylesheet to a text edit widget for custom visual styling, such as colors, borders, and padding.
See also: createTextEdit()
Mudlet VersionAvailable in Mudlet4.21+
Parameters
  • name:
The name of the text edit.
  • css:
A Qt stylesheet string.
Returns
  • true on success, or raises an error if the text edit was not found.
Example
setTextEditStyleSheet("myEditor", [[
QPlainTextEdit {
background-color: #1a1a2e;
color: #e0e0e0;
border: 1px solid #444;
padding: 5px;
}
]])

-- Geyser usage
myEditor:setStyleSheet([[
QPlainTextEdit {
background-color: #1a1a2e;
color: #e0e0e0;
border: 1px solid #444;
padding: 5px;
}
]])

setTextEditFont, PR #8986

setTextEditFont(name, fontName)
Sets the font family of a text edit widget.
See also: setTextEditFontSize()
Mudlet VersionAvailable in Mudlet4.21+
Parameters
  • name:
The name of the text edit.
  • fontName:
The name of the font family to use (e.g. "Ubuntu Mono", "Courier New").
Returns
  • true on success, or raises an error if the text edit was not found.
Example
setTextEditFont("myEditor", "Ubuntu Mono")

-- Geyser usage
myEditor:setFont("Ubuntu Mono")

setTextEditFontSize, PR #8986

setTextEditFontSize(name, size)
Sets the font size of a text edit widget in points.
See also: setTextEditFont()
Mudlet VersionAvailable in Mudlet4.21+
Parameters
  • name:
The name of the text edit.
  • size:
The font size in points.
Returns
  • true on success, or raises an error if the text edit was not found.
Example
setTextEditFontSize("myEditor", 14)

-- Geyser usage
myEditor:setFontSize(14)

setTextEditTabMovesFocus, PR #8986

setTextEditTabMovesFocus(name, state)
Controls whether pressing Tab in the text edit inserts a tab character or moves focus to the next widget. By default, Tab inserts a tab

character.

See also: createTextEdit()
Mudlet VersionAvailable in Mudlet4.21+
Parameters
  • name:
The name of the text edit.
  • state:
true to have Tab move focus to the next widget, false to have Tab insert a tab character (default behavior).
Returns
  • true on success, or raises an error if the text edit was not found.
Example
-- make Tab move focus instead of inserting a tab
setTextEditTabMovesFocus("myEditor", true)

-- Geyser usage
myEditor:setTabMovesFocus(true)

--- And here's the event documentation that should be added to the Events section (if one exists on Area 51), or noted alongside the functions:

sysTextEditDeleted, PR #8986

Raised when a text edit widget is deleted via deleteTextEdit() or when the Geyser wrapper calls its type_delete method.
Arguments
  • arg1: "sysTextEditDeleted"
  • arg2: The name of the deleted text edit.
Example
function onTextEditDeleted(event, name)
echo("Text edit deleted: " .. name .. "\n")
end
registerAnonymousEventHandler("sysTextEditDeleted", onTextEditDeleted)


setBackgroundImage (updated), PR #8935

Note Note: As of Mudlet 4.21, setBackgroundImage() supports SVG files in addition to raster images (PNG, JPG, etc). Simply pass a path to an .svg file. SVG backgrounds can then be transformed using setSvgRotation(), setSvgShear(), and reset with resetSvgTransform(). SVG images are rendered at full resolution regardless of HiDPI scaling.

Example
-- set an SVG background on a label
setBackgroundImage("myLabel", getMudletHomeDir() .. "/compass.svg", 4)

-- rotate it 45 degrees
setSvgRotation("myLabel", 45)

-- Geyser usage
myLabel:setBackgroundImage(getMudletHomeDir() .. "/compass.svg")
myLabel:setSvgRotation(45)

setSvgRotation, PR #8935

setSvgRotation(labelName, angle)
Sets the rotation angle for a label's SVG background image. The SVG is rotated around its center; label text and background color are unaffected. Requires the label to have an
SVG background set via setBackgroundImage().
See also: resetSvgRotation(), setSvgShear(), resetSvgTransform()
Mudlet VersionAvailable in Mudlet4.21+
Parameters
  • labelName:
The name of the label to rotate the SVG background for.
  • angle:
Rotation angle in degrees (positive = clockwise).
Returns
  • true on success, or nil and an error message if the label is not found.
Example
-- rotate the SVG background 45 degrees clockwise
setSvgRotation("myLabel", 45)

-- rotate it upside down
setSvgRotation("myLabel", 180)

-- Geyser usage
myLabel:setSvgRotation(45)

resetSvgRotation, PR #8935

resetSvgRotation(labelName)
Resets the SVG background image rotation to 0 degrees.
See also: setSvgRotation(), resetSvgTransform()
Mudlet VersionAvailable in Mudlet4.21+
Parameters
  • labelName:
The name of the label to reset the SVG rotation for.
Returns
  • true on success, or nil and an error message if the label is not found.
Example
resetSvgRotation("myLabel")

-- Geyser usage
myLabel:resetSvgRotation()

setSvgShear, PR #8935

setSvgShear(labelName, shearX, shearY)
Sets the shear (skew) for a label's SVG background image. The SVG is sheared around its center; label text and background color are unaffected. Requires the label to have an

SVG background set via setBackgroundImage().

See also: resetSvgShear(), setSvgRotation(), resetSvgTransform()
Mudlet VersionAvailable in Mudlet4.21+
Parameters
  • labelName:
The name of the label to shear the SVG background for.
  • shearX:
Horizontal shear factor.
  • shearY:
Vertical shear factor.
Returns
  • true on success, or nil and an error message if the label is not found.
Example
-- apply a horizontal skew to the SVG background
setSvgShear("myLabel", 0.3, 0)

-- apply both horizontal and vertical skew
setSvgShear("myLabel", 0.2, 0.1)

-- Geyser usage
myLabel:setSvgShear(0.3, 0)

resetSvgShear, PR #8935

resetSvgShear(labelName)
Resets the SVG background image shear to (0, 0).
See also: setSvgShear(), resetSvgTransform()
Mudlet VersionAvailable in Mudlet4.21+
Parameters
  • labelName:
The name of the label to reset the SVG shear for.
Returns
  • true on success, or nil and an error message if the label is not found.
Example
resetSvgShear("myLabel")

-- Geyser usage
myLabel:resetSvgShear()

resetSvgTransform, PR #8935

resetSvgTransform(labelName)
Resets all SVG transforms (rotation and shear) back to defaults, but preserves any SVG tint. This is a convenience function equivalent to calling both

resetSvgRotation() and resetSvgShear().

See also: setSvgRotation(), setSvgShear(), resetSvgRotation(), resetSvgShear()
Mudlet VersionAvailable in Mudlet4.21+
Parameters
  • labelName:
The name of the label to reset all SVG transforms for.
Returns
  • true on success, or nil and an error message if the label is not found.
Example
-- reset all transforms at once
resetSvgTransform("myLabel")

-- Geyser usage
myLabel:resetSvgTransform()

getBorderColor, PR #8688

getBorderColor()
Returns the RGB values of the main window border color (the area outside the main console where the mapper, buttons, and other UI elements are placed).
See also: setBorderColor(), getBorderSizes(), getBackgroundColor()
Example
-- get the current border color
local r, g, b = getBorderColor()
echo(string.format("Border color is RGB(%d, %d, %d)\n", r, g, b))

-- check if border is using default black color
local r, g, b = getBorderColor()
if r == 0 and g == 0 and b == 0 then
  echo("Border is black - consider customizing it!\n")
end

-- store current color before changing it
local oldR, oldG, oldB = getBorderColor()
setBorderColor(50, 50, 50)
-- later, restore the original color
setBorderColor(oldR, oldG, oldB)


insertPopup, revised in PR #6925

insertPopup([windowName], text, {commands}, {hints}[{, tool-tips}][, useCurrentFormatElseDefault])
Creates text with a left-clickable link, and a right-click menu for more options at the end of the current line, like echo. The added text, upon being left-clicked, will do the first command in the list. Upon being right-clicked, it'll display a menu with all possible commands. The menu will be populated with hints, one for each line; if a tool-tips table is not provided the same hints will also be listed one-per-line as a tool-tip but if a matching number of tool-tips are provided they will be concatenated to provide a tool-tip when the text is hovered over by the pointer - these tool-tips can be rich-text to produce information formatted with additional content in the same manner as labels.
Parameters
  • windowName:
(optional) name of the window as a string to echo to. Use either main or omit for the main window, or the miniconsole's or user-window's name otherwise.
  • text:
the text string to display.
  • {commands}:
a table of lua code to do, in text strings or as functions (since Mudlet 4.11), i.e. {[[send("hello")]], function() echo("hi!") end}.
  • {hints}:
a table of strings which will be shown on the right-click menu (and popup if no {tool-tips} table is provided). If a particular position in both the commands and hints table are both the empty string "" but there is something in the tool-tips table, no entry for that position will be made in the context menu but the tool-tip can still display something which can include images or text.
  • {tool-tips}:
(optional) a table of possibly rich-text strings which will be shown on the popup if provided.
  • useCurrentFormatElseDefault:
(optional) a boolean value for using either the current formatting options (color, underline, italic and other effects) if true or the link default (blue underline) if false, if omitted the default format is used.

Note Note: Mudlet will distinguish between the optional tool-tips and the flag to switch between the standard link and the current text format by examining the type of the argument, as such this pair of arguments can be in either order.

Example
-- Create some text as a clickable with a popup menu, a left click will ''send "sleep"'':
insertPopup("activities to do", {function() send "sleep" end, function() send "sit" end, function() send "stand" end}, {"sleep", "sit", "stand"})

-- alternatively, put commands as text (in [[ and ]] to use quotation marks inside)
insertPopup("activities to do", {[[send "sleep"]], [[send "sit"]], [[send "stand"]]}, {"sleep", "sit", "stand"})

-- one can also provide helpful information

-- todo: an example with rich-text in the tool-tips(s)

selectAll, PR #8971

selectAll(str, func)
Selects every instance of a string on the cursor's current line and executes a provided function for each one. Useful for applying formatting or other operations to repeated occurrences of a word or phrase.
See also: selectString()
Parameters
  • str:
The string to select every instance of.
  • func:
The function to run for each selected instance of the string.
Example
-- bold every instance of "dragon" on the current line
selectAll("dragon", function()
  setBold(true)
end)
deselect()
resetFormat()

Discord Functions

All functions to customize the information Mudlet displays in Discord's rich presence interface. For an overview on how all of these functions tie in together, see our Discord scripting overview.

Mud Client Media Protocol

All GMCP functions to send sound and music events. For an overview on how all of these functions tie in together, see our MUD Client Media Protocol scripting overview.

Client.Media.Spatial, PR #8452 open

The Client.Media.Spatial family of GMCP packages extends the Client.Media protocol to provide 3D positional audio capabilities. These packages allow games to create immersive spatial soundscapes with positioned audio sources and listener orientation.

Mudlet VersionAvailable in Mudlet4.20+

Enabling Spatial Audio

Spatial audio requires the same settings as regular Client.Media:

  1. The "Enable GMCP" box in the Miscellaneous section must be checked.
  2. The "Allow server to download and play media" box in the Game protocols section must be checked.

Coordinate System

The spatial audio system uses a 3D coordinate system where:

  • Azimuth: Horizontal angle in degrees (0° = front, 90° = right, 180° = back, 270° = left)
  • Elevation: Vertical angle in degrees (-90° = below, 0° = level, 90° = above)
  • Distance: Distance from listener in arbitrary units (1.0 = close, higher values = farther)

For listener positioning, a Cartesian coordinate system is used:

  • X: Left/right position
  • Y: Forward/back position
  • Z: Up/down position

Playing Spatial Media

Send Client.Media.Spatial.Play GMCP events to play positioned sound sources in 3D space.

Required Parameter Value Default Description
Yes "key" <string> Unique identifier for this spatial audio source. Used for updates and stopping.
Yes "name" <file name> Name of the media file. May contain directory information (i.e. ambient/wind.wav).
Maybe "url" <url> Resource location where the media file may be downloaded. Only required if not using Client.Media.Default URL or if file is not cached.
No "volume" 1 to 100 80 Volume level relative to the master spatial audio volume.
No "loops" -1, or >= 1 1 Number of times to loop the sound. -1 for infinite looping.
No "position" <array or object> 3D position of the sound source. See position formats below.
No "occlusion" 0.0 to 1.0 0.0 Occlusion factor (0.0 = no occlusion, 1.0 = fully occluded/muffled).
No "room" <object> Room acoustics parameters. See room acoustics below.

Position Formats

Position can be specified as an array [azimuth, elevation, distance] or as an object:

// Array format
Client.Media.Spatial.Play {
  "key": "footsteps_player",
  "name": "footsteps.wav",
  "position": [45, 0, 2.5]
}

// Object format  
Client.Media.Spatial.Play {
  "key": "footsteps_player", 
  "name": "footsteps.wav",
  "position": {
    "azimuth": 45,
    "elevation": 0,
    "distance": 2.5
  }
}

Room Acoustics

Room acoustics can enhance spatial realism by simulating reverb and reflections:

Client.Media.Spatial.Play {
  "key": "cave_drip",
  "name": "water_drop.wav",
  "position": [0, -45, 8],
  "room": {
    "dimensions": [20, 30, 5],
    "reverb": 2.5,
    "reflection": 1.8,
    "material": "sheetrock"
  }
}

Updating Spatial Media

Send Client.Media.Spatial.Update GMCP events to modify properties of already playing spatial audio sources.

Required Parameter Value Description
Yes "key" <string> Unique identifier of the spatial audio source to update.
No "position" <array or object> New 3D position for the sound source.
No "volume" 1 to 100 New volume level.
No "occlusion" 0.0 to 1.0 New occlusion factor.

Example of moving a sound source:

Client.Media.Spatial.Update {
  "key": "footsteps_player",
  "position": [90, 0, 3.0],
  "volume": 60
}

Stopping Spatial Media

Send Client.Media.Spatial.Stop GMCP events to stop spatial audio sources.

Required Parameter Value Description
No "key" <string> Unique identifier of the spatial audio source to stop. If omitted, stops all spatial audio.

Stop a specific source:

Client.Media.Spatial.Stop {
  "key": "footsteps_player"
}

Stop all spatial audio:

Client.Media.Spatial.Stop {}

Listener Control

Send Client.Media.Spatial.Listener GMCP events to control the listener's position and orientation in 3D space.

Required Parameter Value Description
No "position" <array or object> Listener position in 3D space [x, y, z] or {x, y, z}.
No "rotation" <array or object> Listener rotation [yaw, pitch, roll] or {yaw, pitch, roll} in degrees.

Listener Position

Position uses Cartesian coordinates:

// Array format: [x, y, z]
Client.Media.Spatial.Listener {
  "position": [5.0, -2.0, 1.5]
}

// Object format
Client.Media.Spatial.Listener {
  "position": {
    "x": 5.0,
    "y": -2.0, 
    "z": 1.5
  }
}

Listener Rotation

Rotation controls where the listener is facing:

// Array format: [yaw, pitch, roll]
Client.Media.Spatial.Listener {
  "rotation": [180, -10, 0]
}

// Object format
Client.Media.Spatial.Listener {
  "rotation": {
    "yaw": 180,
    "pitch": -10,
    "roll": 0
  }
}

Usage Examples

Basic Positioned Sound

Client.Media.Spatial.Play {
  "key": "sword_clash",
  "name": "combat/sword_hit.wav",
  "position": [30, 0, 2],
  "volume": 85
}

Moving Ambient Sound

// Start a moving creature sound
Client.Media.Spatial.Play {
  "key": "wolf_howl",
  "name": "creatures/wolf_howl.wav", 
  "position": [-90, 10, 5],
  "loops": 3
}

// Update its position as it moves
Client.Media.Spatial.Update {
  "key": "wolf_howl",
  "position": [-45, 5, 3]
}

Environmental Audio with Room Acoustics

Client.Media.Spatial.Play {
  "key": "cathedral_organ",
  "name": "music/organ_chord.wav",
  "position": [0, 15, 20],
  "volume": 70,
  "room": {
    "dimensions": [40, 80, 15],
    "reverb": 3.0,
    "reflection": 2.2,
    "material": "sheetrock"
  }
}

Dynamic Listener Movement

// Player turns to face north and moves forward
Client.Media.Spatial.Listener {
  "position": [0, 5, 0],
  "rotation": [0, 0, 0]
}

// Player turns to look northeast and up slightly
Client.Media.Spatial.Listener {
  "rotation": [45, 15, 0]
}

Note Note: Spatial audio provides the most immersive experience when used with headphones or properly positioned stereo speakers. The 3D positioning effects may be less apparent with poor audio hardware.

GMCP Spatial Audio Capability Detection

Mudlet provides three GMCP messages for server developers to detect and query spatial audio capabilities. These allow servers to adapt their audio content based on the client's actual capabilities and current settings.

Client.Media.Spatial.Capabilities

Query the client's spatial audio capabilities to determine what features and formats are supported.

Server sends:

Client.Media.Spatial.Capabilities {}

Client responds with:

Client.Media.Spatial.Capabilities {
  "version": "1.0",
  "formats": ["wav", "mp3", "ogg", "flac", "aac", "m4a"],
  "output_modes": ["stereo", "surround", "headphone"],
  "room_materials": ["brick", "concrete", "wood", "metal", "glass", ...],
  "max_sources": 32,
  "distance_model": "inverse",
  "coordinate_system": "spherical",
  "features": {
    "positioning": true,
    "room_acoustics": true,
    "occlusion": true,
    "listener_control": true,
    "test_tones": true,
    "volume_control": true,
    "loops": true
  }
}

Response Fields:

  • version: Protocol version (currently "1.0")
  • formats: Array of supported audio file formats (detected at runtime based on Qt6 multimedia backend)
  • output_modes: Available audio output configurations
  • room_materials: Supported acoustic materials for room simulation
  • max_sources: Maximum number of simultaneous spatial audio sources
  • distance_model: Audio attenuation model used ("inverse")
  • coordinate_system: Position coordinate system ("spherical" with azimuth/elevation/distance)
  • features: Boolean flags indicating supported capabilities
Room Materials

The room materials are baed on this list from Qt QAudioRoom's Material enum. Mudlet is accepting the list of aliases as well.

Material Aliases Description
transparent air The side of the room is open and won't contribute to reflections or reverb.
acousticceilingtiles acoustictiles Acoustic tiles that suppress most reflections and reverb.
brickbare brick Bare brick wall.
brickpainted Painted brick wall.
concreteblockcoarse concrete Raw concrete wall
concreteblockpainted concretepainted Painted concrete wall
curtainheavy curtain, fabric Heavy curtain. Will mostly reflect low frequencies
fiberglassinsulation fiberglass, carpet Fiber glass insulation. Only reflects very low frequencies
glassthin glass Thin glass wall
glassthick Thick glass wall
grass Grass
linoleumonconcrete linoleum Linoleum floor
marble Marble floor
metal Metal
parquetonconcrete parquet, parquetonfiberboard Parquet wooden floor on concrete
plasterrough plaster Rough plaster
plastersmooth Smooth plaster
plywoodpanel plywood Plywood panel
polishedconcreteortile tile, polishedconcrete Polished concrete or tiles
sheetrock stone Rock
wateroricesurface water, ice, wateroriceereflector Water or ice
woodceiling Wooden ceiling
woodpanel wood Wooden panel
uniformmaterial uniform Artificial material giving uniform reflections on all frequencies

Client.Media.Spatial.Settings

Query the client's current spatial audio configuration and user settings.

Server sends:

Client.Media.Spatial.Settings {}

Client responds with:

Client.Media.Spatial.Settings {
  "master_volume": 80,
  "listener": {
    "position": [0, 0, 0],
    "rotation": [0, 0, 0]
  },
  "room": {
    "dimensions": [10, 10, 4],
    "reverb_gain": 0.3,
    "reflection_gain": 0.5,
    "reverb_time": 1.0,
    "reverb_brightness": 0.0
  }
}

Response Fields:

  • master_volume: Current master volume (0-100)
  • listener.position: 3D listener position [x, y, z] in Cartesian coordinates
  • listener.rotation: Listener orientation [yaw, pitch, roll] in degrees
  • room.dimensions: Room size [width, height, depth] in meters
  • room.reverb_gain: Reverberation intensity (0.0-5.0)
  • room.reflection_gain: Early reflection intensity (0.0-5.0)
  • room.reverb_time: Reverberation decay time in seconds (0.0-20.0)
  • room.reverb_brightness: Reverb frequency bias (-1.0 to 1.0)

Client.Media.Spatial.Status

Query the current status of active spatial audio sources managed by the server.

Server sends:

Client.Media.Spatial.Status {}

Client responds with:

Client.Media.Spatial.Status {
  "engine_initialized": true,
  "source_count": 2,
  "max_sources": 32,
  "active_sources": [
    {
      "key": "ambient_forest",
      "status": "playing",
      "position": [45, 0, 5],
      "volume": 60,
      "occlusion": 0.0,
      "size": 1.0,
      "loops": -1
    },
    {
      "key": "footsteps",
      "status": "paused",
      "position": [-90, -10, 2],
      "volume": 80,
      "occlusion": 0.5,
      "size": 0.5,
      "loops": 1
    }
  ]
}

Response Fields:

  • engine_initialized: Whether the spatial audio engine is ready
  • source_count: Number of currently active sources
  • max_sources: Maximum supported sources
  • active_sources: Array of source objects with current state
    • key: Server-assigned source identifier
    • status: Current playback state ("playing", "paused", "stopped")
    • position: Source position [azimuth, elevation, distance] in spherical coordinates
    • volume: Current volume level (0-100)
    • occlusion: Occlusion/obstruction level (0.0-4.0)
    • size: Source spatial size (0.0+, affects audio spread)
    • loops: Loop count (-1 for infinite, 0 for stopped, >0 for remaining loops)

Supported Protocols

MXP FRAME and DEST Tags, PR #8577 4.21

MXP FRAME and DEST tags allow MUD servers to create multi-window layouts, directing game output to separate panels within Mudlet. This enables rich interfaces with dedicated areas for chat, inventory, status, and more.

Mudlet VersionAvailable in Mudlet4.21+

Overview

FRAME creates a new window panel that can display game content.

DEST (destination) redirects subsequent text to a specific frame.

FRAME Tag

Creates a named frame where content can be directed.

Basic Syntax

<FRAME name="frameName" title="Frame Title" align="left" width="25%" height="100%">

Attributes

Attribute Required Description Default
NAME Yes Unique identifier for the frame -
TITLE No Display title shown in the frame's tab Same as NAME
INTERNAL No Frame appears inside the main window Yes (default)
EXTERNAL No Frame appears as a separate floating window No
FLOATING No Borderless frame without title bar No
ALIGN No Position: left, right, top, bottom left
LEFT No Absolute horizontal position (pixels, %, or 'c' suffix) -
TOP No Absolute vertical position (pixels, %, or 'c' suffix) -
WIDTH No Frame width (pixels, %, or characters with 'c' suffix) 25%
HEIGHT No Frame height (pixels, %, or characters with 'c' suffix) 25%
SCROLLING No Enable scrolling (YES/NO) YES
ACTION No open (show), close (hide), or focus open

Size Units

  • Pixels: 300px or 300
  • Percentage: 25% of available space
  • Characters: 40c for 40 characters width/height

CMUD Extension: DOCK

Note: The DOCK attribute is a CMUD extension, not part of the official MXP 1.0 specification.
<FRAME name="tab2" INTERNAL align="client" DOCK="parentFrame">

When used with align="client", the DOCK attribute creates a tabbed frame inside an existing frame.

Examples

Create a left-aligned chat panel:

<FRAME name="chat" title="Chat" align="left" width="30%">

Create a status bar at the bottom:

<FRAME name="status" align="bottom" height="50">

Create a borderless floating frame:

<FRAME name="minimap" FLOATING width="200" height="200" LEFT="10" TOP="10">

Show an existing frame:

<FRAME name="chat" action="open">

Close a frame:

<FRAME name="chat" action="close">

DEST Tag

Redirects text output to a named frame.

Syntax

<DEST name="frameName">Text goes here</DEST>

Or as a self-closing tag to set destination for all following text:

<DEST name="frameName">

Attributes

Attribute Required Description
NAME Yes Target frame name (empty string returns to main window)
EOF No If present, clears all content in the frame before writing
EOL No If present, clears the current line before writing

Examples

Send text to a chat frame:

<DEST name="chat">Player says: Hello!</DEST>

Clear frame and write new content:

<DEST name="status" EOF>HP: 100/100  MP: 50/50</DEST>

Return output to main window:

<DEST name="">

Complete Example

A typical MXP sequence to set up a multi-panel interface:

<!-- Create the frames -->
<FRAME name="chat" title="Chat" align="left" width="25%">
<FRAME name="status" title="Status" align="bottom" height="3c">

<!-- Send content to chat frame -->
<DEST name="chat">
[Guild] Bob: Anyone want to group?
[Guild] Alice: Sure!
</DEST>

<!-- Send content to status frame, clearing previous content -->
<DEST name="status" EOF>HP: 100/100 | MP: 75/100 | XP: 45%</DEST>

<!-- Return to main window for regular game output -->
<DEST name="">
You are standing in the town square.

Behavior Notes

  • Frames persist until explicitly closed with action="close"
  • Re-opening an existing frame shows it without changing its size or position (respects user customization)
  • Frame names are case-sensitive
  • Content sent via DEST inherits the formatting of the destination frame
  • Frames reset on reconnect (don't persist between sessions)

See Also

Events

New or revised events that Mudlet can raise to inform a profile about changes. See Mudlet-raised events for the existing ones.

UI Functions

All functions that help you construct custom GUIs. They deal mainly with miniconsole/label/gauge creation and manipulation as well as displaying or formatting information on the screen.

setTextFormat, PR #8983, open

setTextFormat(windowName, r1, g1, b1, r2, g2, b2, bold, underline, italics, [strikeout], [overline], [reverse], [blink])
Sets current text format of selected window. This is a more convenient means to set all the individual features at once compared to using setFgColor(windowName, r,g,b), setBold(windowName, true), setItalics(windowName, true), setUnderline(windowName, true), setStrikeOut(windowName, true).
See Also: getTextFormat()
Parameters
  • windowName
Specify name of selected window. If empty string "" or "main" format will be applied to the main console
  • r1,g1,b1
To color text background, give number values in RBG style
  • r2,g2,b2
To color text foreground, give number values in RBG style
  • bold
To format text bold, set to 1 or true, otherwise 0 or false
  • underline
To underline text, set to 1 or true, otherwise 0 or false
  • italics
To format text italic, set to 1 or true, otherwise 0 or false
  • strikeout
(optional) To strike text out, set to 1 or true, otherwise 0 or false or simply no argument
  • overline
(optional) To use overline, set to 1 or true, otherwise 0 or false or simply no argument
  • reverse
(optional) To swap foreground and background colors, set to 1 or true, otherwise 0 or false or simply no argument
  • blink
(optional) To make text blink, use "slow", "fast", or "none" (default). Requires "Enable blinking text" in Settings → Accessibility.
Example
--This script would create a mini text console and write with bold, struck-out, yellow foreground color and blue background color "This is a test".
createMiniConsole( "con1", 0,0,300,100);
setTextFormat("con1",0,0,255,255,255,0,true,0,false,1);
echo("con1","This is a test")

Note Note: In versions prior to 3.7.0 the error messages and this wiki were wrong in that they had the foreground color parameters as r1, g1 and b1 and the background ones as r2, g2 and b2.

MudMaster Chat Protocol (MMCP)

MMCP is a peer-to-peer protocol enabling out of band data to be sent to connected peers in the form of public or private chats, or general data.
See MMCP protocol documentation at: https://tintin.mudhalla.net/protocols/mmcp/
And at: https://mudstandards.org/mud/mmcp/
See also https://wiki.mudlet.org/w/Notes_on_MMCP
Mudlet VersionAvailable in Mudlet ?.??+

Note Note: pending, not yet available. See https://github.com/Mudlet/Mudlet/pull/7765

Lua functions

  • Note for all functions using target as an argument, the argument can be the client chatname or ID of the client as seen in mmcp.displayClientList()

accept

mmcp.accept(target)
Accepts an incoming connection request.
Parameters
  • target:
  • Incoming client name or ID.

Note Note: pending, not yet available in initial MMCP release.

allowSnoop

mmcp.allowSnoop(target)
Toggles the Allow Snoop flag for a client, allowing them to initiate a snoop request which will forward them text you see from your game.
Parameters
  • target:
  • Client name or ID.

call

mmcp.call(host[, port])
Initiate an outgoing connection to another client.
Parameters
  • host:
  • IPv4 address or fully qualified domain name.
  • port:
  • (optional) Port to connect to. Default 4050.
Example
local host = "1.2.3.4"
local port = 4050

mmcp.call(host, port)
You should see the following message
[ CHAT ]  - Connecting to 1.2.3.4:4050...
[ CHAT ]  - Waiting for response from 1.2.3.4:4050...
[ CHAT ]  - Connection to ChatClient at 1.2.3.4:4050 accepted.

chatAll

mmcp.chatAll(message)
Sends a message to all connected clients.
Parameters
  • message:
  • The message to send.

This message will have the format of: <Name> chats to everybody, '<message>'

You will see the message: You chat to everybody, '<message>'

Example
-- Alias: ChatAll
-- Alias Pattern: ^chatAll (.*)

mmcp.chatAll(matches[2])

chatGroup

mmcp.chatGroup(group, message)
Sends a message to all clients in the specified group.
Parameters
  • group:
  • The group to send the message to.
  • message:
  • The message to send.

The message will have the format of: <Name> chats to the group, '<message>'

You will see the message: You chat to <<group>>, '<message>'

Example
-- Alias: ChatGroup
-- Alias Pattern: ^chatGroup ([a-zA-Z0-9]+) (.*)

mmcp.chatGroup(matches[2], matches[3])

chatName

mmcp.chatName([name])
Sets or gets your current chat name visible to other clients.
Parameters
  • name:
  • (optional) The new chat name to use.
Returns
  • If no name is specified, this function will return your current chat name.
Example
-- Alias: ChatName
-- Alias Pattern: ^chatName (\S+)

mmcp.chatNAme(matches[2])

chatTo

mmcp.chatTo(target, message)
Sends a message to a specific client.
Parameters
  • target:
  • Client name or ID.
  • message:
  • The message to send.

The target client will see the message: <Name> chats to you, '<message>'

You will see the message: You chat to <target>, '<message>'

Example
-- Alias: ChatTo
-- Alias Pattern: ^chatTo (.*?) (.*)

mmcp.chatTo(matches[2], matches[3])

deny

mmcp.deny(target)
Denies an incoming connection request.
Parameters
  • target:
  • Client name or ID.

Note Note: pending, not yet available in initial MMCP release.

disconnect

mmcp.disconnect(target)
Disconnects a connected client.
Parameters
  • target:
  • Client name or ID

displayClientList

mmcp.displayClientList()
Displays a table showing information about connected and pending clients.
Example
mmcp.displayClientList()
You should see the following table
Id   Name                 Address              Port  Group           Flags    ChatClient
==== ==================== ==================== ===== =============== ======== ================
   1 ChatClient           1.2.3.4              4050                           Mudlet
==== ==================== ==================== ===== =============== ======== ================
Color Key: Connected  Pending
Flags:  F - Firewall,       I - Ignored,  P - Private,  S - Serving
        n - Allow Snooping, N - Being Snooped
  • You may then use the mmcp lua commands referencing the client by their ID (1) or name (ChatClient)

emoteAll

mmcp.emoteAll(message)
Sends an emote message to all connected clients.
Parameters
  • message:
  • The message to send.

Clients will see the message: <Name> <message>

If you have enabled the "Prefix emote messages" option, you will see: You emote to everyone: '<Name> <message>'

Otherwise you will see: <Name> <message>

Example
-- Alias: EmoteAll
-- Alias Pattern: ^emoteAll (.*)

local emoteStr = "says, '" .. matches[2] .. "'"

mmcp.emoteAll(emoteStr)

getClientFlags

mmcp.getClientFlags(target)
Returns a string containing the client flags of a connected client.
Parameters
  • target:
  • Client name or ID.
Returns

This string is an 8 character string where letters in the following positions have meaning: "12345678"

  • 1: Reserved, blank
  • 2: Reserved, blank
  • 3: P if the connection is Private, otherwise blank
  • 4: I if the connection is Ignored, otherwise blank
  • 5: S if the client is being served, otherwise blank
  • 6: F if the client is firewalled, otherwise blank
  • 7: N if the client is snooping us, n if the client can snoop us, otherwise blank
  • 8: Reserved, blank

getClientList

clientInfo = mmcp.getClientList()
Returns a lua table containing the following information for each connected client.
Returns
  • id: The ID of the client
  • name: The chat name of the client.
  • host: The hostname or IP address of the client.
  • port: The port the client (check if this is the reported port or port they connected from)
  • version: The MUD client and version of the client
Example
local clientList = mmcp.getClientList()
The variable clientList will then contain
{ {
    host = "1.2.3.4",
    id = 1,
    name = "ChatClient",
    port = 4050,
    version = "Mudlet"
  } }

ignore

mmcp.ignore(target)
Toggles ignoring a client. You will not see chat messages from an ignored client.
Parameters
  • target:
  • Client name or ID.

peek

mmcp.peek(target)
Sends a request to the target client to peek at their connection list.
Parameters
  • target:
  • Client name or ID.

ping

mmcp.ping(target)
Sends a ping request to a client.
Parameters
  • target:
  • Client name or ID.

request

mmcp.request(target)
Sends a request to the target client to request and connect to their public connections.
Mudlet will attempt to connect to each of the hosts returned by this command.
Parameters
  • target:
  • Client name or ID.

sendSideChannel

mmcp.sendSideChannel(channel, message)
Sends an OOB (Out of band) message to all connected clients.
Parameters
  • channel:
  • The channel to send the message on.
  • message:
  • The message to send.

Note Note: This will only send to other Mudlet clients. Upon receipt of this message Mudlet will raise a sysMMCPSideChannelMessage containing the channel and message data.

Example
-- myStats populated by prompt trigger
local outStr = string.format("%s,%d,%d,%d,%d,%s",
  mmcp.chatName(), myStats.hp, myStats.maxHp, myStats.mana, myStats.maxMana, myStats.buffs)

mmcp.sendSideChannel("stats", outStr)

-- Example event handling
function handleStatsData(statsTable)
  -- this should match the outStr format sent by mmcp.sendSideChannel
  local name, hp, maxHp, mana, maxMana, buffs = string.match(statsTable, "(%S+),(%d+),(%d+),(%d+),(%d+),(.*)")

  -- do things with this data
end

function VStats.eventHandler(event, ...)
  if event == "sysMMCPSideChannelMessage" then
    if arg[2] == "stats" then -- check if its actually for us, arg[2] should match "stats" provided to mmcp.sendSideChannel
      handleStatsData(arg[3])
    end
  end
end

registerAnonymousEventHandler("sysMMCPSideChannelMessage", "VStats.eventHandler")

serve

mmcp.serve(target)
Toggles the serve flag for a client, this clients chat messages will be sent to all other connected clients.
Parameters
  • target:
  • Client name or ID.

setDoNotDisturb

mmcp.setDoNotDisturb(target)
Toggles the Do Not Disturb flag, any incoming connections will be automatically denied.

Note Note: pending, not yet available in initial MMCP release.

setGroup

mmcp.setGroup(target, group)
Assigns a client to a chat group.
Parameters
  • target:
  • Client name or ID.
  • group:
  • The group name.

Note Note: The group name is only visible to you.

Example
-- Alias: SetGroup
-- Alias Pattern: ^setGroup ([a-zA-Z0-9]+) ?(.*)

mmcp.setGroup(matches[2], matches[3])

setPrivate

mmcp.setPrivate(target)
Toggles the private flag for a client. Any clients set as private will not be listed in peek or connection requests.
Parameters
  • target:
  • Client name or ID.

snoop

mmcp.snoop(target)
Starts snooping a client. The target may first need to enable snooping on their client.
Parameters
  • target:
  • Client name or ID.
Example
-- Alias: ChatSnoop
-- Alias Pattern: ^chatSnoop (.*)

mmcp.snoop(matches[2])

startServer

mmcp.startServer([port])
Starts accepting connections from clients.
Parameters
  • port:
  • (optional) Port to listen for incoming connections. Default 4050.

Note Note: pending, not yet available in initial MMCP release.

stopServer

mmcp.stopServer()
Stops accepting connections from clients.

Note Note: pending, not yet available in initial MMCP release.

Events

sysMMCPChatMessage

Raised when Mudlet receives an MMCP chat message from a client
Arguments
  • peerName:
  • Peer Name of the client
  • message:
  • Message content
Example putting MMCP messages into an EMCO window
function mmcpEventHandler(event, ...)
    -- trim whitespace
    local trimmedStr = arg[1]:match("^%s*(.-)%s*$")

    myEmcoWindow:decho("MMCP", ansi2decho(trimmedStr) .. "\n", false)
end

registerNamedEventHandler(getProfileName(), "MMCP", "sysMMCPChatMessage", "mmcpEventHandler")

sysMMCPSideChannelMessage

Raised when an MMCP side channel message has been received
Arguments
  • peerName:
  • Peer Name of the client
  • channel:
  • Channel identifier string
  • message:
  • Message content
Example
function VFrame.eventHandler(event, ...)
  if event == "gmcp.Char.Vitals" then

    VFrame.updateVitals()

  elseif event == "sysMMCPSideChannelMessage" then
    --display("event: " .. event .. " from: " .. arg[1] .. " channel: " .. arg[2] .. " message: " .. arg[3])
    if arg[2] == "stats" and myVFrame then -- check if its actually for us
      myVFrame:playerData(arg[3])
    end
  end
   
end

VFrame.registeredEvents = {
  registerAnonymousEventHandler("gmcp.Char.Vitals", "VFrame.eventHandler"),
  registerAnonymousEventHandler("sysMMCPSideChannelMessage", "VFrame.eventHandler"),
}

sysMMCPIncomingSnoopMessage

Raised when an MMCP client whom you are snooping has sent new snoop data
Arguments
  • peerName:
  • Peer Name of the client
  • message
  • Snoop content

sysMMCPPeerUpdateEvent

Raised when an MMCP client has been connected or disconnected
Arguments
  • peerName:
  • Chat name of the client

Security

Standards

GMCP Extension for MUD Client Authentication

This document defines a GMCP extension that lets MUD clients automate sign-in around the game's own login screen: sending stored credentials, opening browser sign-in links, and reconnecting password-lessly with a server-minted token. Version 2 adds OAuth-based sign-in and the reconnect token to the password login defined by version 1.

Rationale

Different MUDs have diverse login command formats, making it challenging for MUD clients to handle login consistently. This extension provides a standardized way to exchange authentication information through GMCP, simplifying client development and improving compatibility.

Two principles shape the design:

  • The game owns the sign-in screen; the client owns automation. Every game must keep a textual, interactive login for the many clients that do not implement this extension, so that screen always exists and is always the most complete presentation of the game's account model — its providers, its character creation, its wording. This extension does not ask clients to replicate any of it. Instead it gives clients four machine-readable hooks around that screen: autofill stored credentials, open a sign-in URL the server pushes, save a reconnect token, and learn the outcome.
  • A minimal client floor. A conformant client needs no OAuth machinery and no sign-in UI of its own. It opens URLs in a browser and stores an opaque string. A first-time sign-in happens once per player; reconnects happen thousands of times — the hooks optimize the common case.

Versioning

This extension is negotiated through the module version in Core.Supports.Set / Core.Supports.Add: a client advertises the highest major version it understands, for example Core.Supports.Set ["Char.Login 2", ...].

Because base GMCP negotiation is one-directional, the server reports the version it is speaking back to the client in a version field on Char.Login.Default — the effective negotiated version, min(client's advertised version, server's highest supported version). A client treats the absence of the field as version 1, and SHOULD echo the version it is acting on in a version field on each message it sends, so the server can reject a mismatch cleanly (with Char.Login.Result {"success": false, "message": "Unsupported Char.Login version"}) rather than misread a payload.

  • Version 1 defined password-only login: Char.Login.Default (with type and, for OAuth servers, a location), Char.Login.Credentials, and Char.Login.Result as the response to a credentials attempt.
  • Version 2 (this document) adds four things: the version report/echo; the empty Char.Login.Credentials {} defined as an explicit, immediate hand-off to the server's interactive sign-in; the Char.Login.URL push that lets the server open the player's browser at the right moment; and the password-less reconnect pair, Char.Login.Token / Char.Login.Reconnect. It broadens Char.Login.Result into the terminal message of every flow, and relaxes location from required to optional (it now belongs to the optional client-driven appendix). A new major version is warranted because version 2 adds a client→server message and changes the scope of an existing one; a client must opt in by version before a server uses them.

Backward compatibility. A version 2 server remains interoperable with a version 1 client: it offers password-credentials, omits the version 2 fields and messages, and treats the exchange as version 1. In all cases a client may send Char.Login.Credentials {} to fall back to interactive login.

Design

The extension uses the Char.Login namespace with the following messages.

Server-side

Char.Login.Default

Sent in response to Client.Supports.Set, or as soon as telnet negotiation completes. It reports the negotiated version and the supported authentication methods.

  • version (integer, optional; sent by version 2+ servers): the negotiated Char.Login version for this session, min(client's advertised version, server's highest supported version). A positive, non-zero integer. Absent means version 1.
  • type (array of strings, required): the supported method(s), ordered by the server's preference, most-preferred first:
    • password-credentials: traditional username/password login.
    • oauth: sign-in completes through the player's web browser (external identity providers, brokered by the server).
 Char.Login.Default {"version": 2, "type": ["oauth", "password-credentials"]}

A version 1 exchange omits version:

 Char.Login.Default {"type": ["password-credentials"]}

A server that is itself an OpenID Provider may include the additional fields defined in the client-driven appendix on an encrypted connection.

Note: the server does not enumerate its identity providers here, and the client does not choose one over GMCP. The provider choice happens where it always has — on the game's own sign-in screen (see below).

Char.Login.URL

Pushed by the server whenever the sign-in reaches a browser step — typically the moment the player picks a provider on the game's own login screen. A capable client opens the URL in the system browser so the player does not have to click or copy anything; the server SHOULD also print the URL in its own text (clickable where the client renders links, copy/paste otherwise), which remains the universal fallback for clients that ignore this message.

  • url (string, required): the fully-formed authorization URL to open in the player's default browser. The server constructs it (provider, scopes, redirect URI, PKCE, etc.); the client performs no OAuth machinery.
  • nonce (string, optional): an opaque, single-use value tying this browser sign-in to the current session, so the server can correlate the provider callback with this connection. Not a usable credential on its own, so this message is safe over plain telnet.
  • provider (string, optional): the id of the provider this URL signs in with (for example discord), letting the client label the handoff ("Opening your browser to sign in with Discord…").
 Char.Login.URL {"url": "https://example.com/oauth/start?provider=discord&nonce=abc123", "nonce": "abc123", "provider": "discord"}

Consent and safety. A client MUST open only http/https URLs from this message, and only in the system browser — never hand other URI schemes to the operating system. Because the message can arrive unprompted, the client SHOULD confirm user intent before auto-opening; a practical test is whether the user has sent any input on this connection (their provider choice on the game's screen is what triggered the push). Absent that evidence, present the URL as a link to click or copy instead, so a hostile or misbehaving server cannot pop a browser at an idle player.

Char.Login.Token

An opaque, server-minted reconnect credential the client stores in its profile for password-less reconnects. The server sends it at its discretion after a successful sign-in and may re-send it on each successful reconnect when it rotates tokens. Whether to ask the player first ("stay signed in on this device?") is the server's decision, gathered in its own flow; the client's job is mechanical — save what arrives, overwrite on rotation, and offer a local way to forget it.

  • account (string, required): the character or account the token authenticates, in the same form as the account field of Char.Login.Credentials (for example myaccount:mycharacter). A server with multi-character accounts SHOULD mint the character-qualified form: the client replays it verbatim, so a later Char.Login.Reconnect can land directly in the character this device last played — a truly screenless reconnect — with the character roster as the fallback whenever the named character no longer belongs to the account.
  • token (string, required): an opaque, server-owned bearer secret. The client stores it verbatim and never interprets it.
 Char.Login.Token {"account": "myaccount:mycharacter", "token": "opaque-reconnect-token"}

The identity provider's own access and refresh tokens never reach the client or the wire — only this opaque, server-minted reconnect token does.

Char.Login.Result

The terminal message of every authentication flow — password, browser sign-in, and reconnect. The server sends it once the outcome is known.

  • success (boolean, required): whether authentication succeeded. May be sent as a string ("true" / "false") for compatibility with aged MUD drivers.
  • message (string, required when success is false): a human-readable explanation, such as "Invalid credentials" or "Reconnect token expired".
 Char.Login.Result {"success": true}
 Char.Login.Result {"success": false, "message": "Invalid credentials"}

A rejected sign-in or reconnect token is reported this way and never falls through to a password or password-creation prompt.

Client-side

Each client→server message MAY carry a version field echoing the version received in Char.Login.Default; a version 2 client SHOULD include it.

Char.Login.Credentials

After receiving Char.Login.Default with type including password-credentials, a client holding stored credentials sends them. A complete stored pair is the player's explicit choice of sign-in method: it is the client's default, taking precedence over a saved reconnect token (see Guidance for clients). A partial pair — a password without a name, or vice versa — is never sent and never blocks the other methods.

  • account (string, required): character name or player account name. For games that implement both, the character name follows a colon (:), for example myaccount:mycharacter.
  • password (string, required): the password. Servers are encouraged to implement TLS over telnet so passwords transit securely.
  • version (integer, optional): the negotiated version the client is acting on.

Resume form. A client that previously completed a browser sign-in — and therefore learned its provider from the provider field of Char.Login.URL — may instead send the account with that remembered provider and no password:

 Char.Login.Credentials {"account": "myaccount:mycharacter", "provider": "discord", "version": 2}

This asks the server to restart the browser sign-in for that provider directly, replying with Char.Login.URL, so a player whose reconnect token has expired or been revoked is never sent back to a provider menu to answer "which provider did I use here?". The client remembers the provider on the player's behalf, in the same protected store as the reconnect token, and discloses nothing the server did not itself send on this connection's predecessors; the server treats the value only as the flow to start (account–provider binding is verified after the browser sign-in completes, exactly as in the interactive flow, so the resume form confirms nothing about which provider an account uses).

A worked example is given in the resume flow.

A client with nothing to send — or one that simply prefers the game's own screen — hands off with an empty object:

 Char.Login.Credentials {}

This is the explicit interactive hand-off: "the user will sign in on your screen — proceed with your interactive login now." It may be sent in response to any advertised method. A server that receives it SHOULD begin its interactive sign-in immediately rather than wait longer; a client that holds no stored token, provider, or credentials SHOULD send it promptly after Char.Login.Default.

Silence is not a hand-off. A client that advertised Char.Login but has sent nothing yet may be gathering credentials — typically prompting the player for a password it deliberately does not store, a version 1 behaviour that remains fully valid. The server SHOULD wait a generous window (long enough for a player to type) before falling back to its interactive screen, and waiting silently is correct: the player is looking at their client's prompt, not the game's. Only the explicit {} — or a completed attempt — ends the wait early.

Char.Login.Reconnect

On a later connection, a client that previously stored a Char.Login.Token replays it to log in without a browser or password:

  • account (string, required): the saved account.
  • token (string, required): the saved opaque token.
  • version (integer, optional): the negotiated version the client is acting on.
 Char.Login.Reconnect {"account": "myaccount:mycharacter", "token": "opaque-reconnect-token"}

The server replies with Char.Login.Result. On success it may rotate the token by sending a fresh Char.Login.Token. On failure the client discards the saved token and falls back to the interactive hand-off; a rejected token never falls through to a password path.

Messages at a glance

Direction Message Payload Purpose
Server → Client Char.Login.Default {"version"?: <int>, "type": [...]} report negotiated version and supported methods
Client → Server Char.Login.Credentials {"account": "<id>", "password": "<secret>", "version"?: <int>}, {"account": "<id>", "provider": "<id>", "version"?: <int>}, or {} autofill stored credentials, resume the remembered provider's browser sign-in, or hand off to the game's screen
Server → Client Char.Login.URL {"url": "<authorize-url>", "nonce"?: "<id>", "provider"?: "<id>"} open this in the system browser
Server → Client Char.Login.Token {"account": "<id>", "token": "<opaque>"} a reconnect credential to save in the profile
Client → Server Char.Login.Reconnect {"account": "<id>", "token": "<opaque>", "version"?: <int>} replay the saved token to log in with no screen at all
Server → Client Char.Login.Result {"success": <bool>, "message"?: "<text>"} terminal status of any flow

(The optional client-driven appendix adds fields to Char.Login.Default and one client→server message, Char.Login.AuthCode.)

Who owns the sign-in screen

The server presents all sign-in choices — which providers exist, in what order, with what wording — on its own interactive screen, exactly as it does for clients without this extension. Plain numbered menus work everywhere; games may enhance them with clickable links (for example OSC 8 hyperlinks) where the connecting client supports them. Those presentation niceties are negotiated out of band and are orthogonal to this extension.

The client contributes what only it can do, silently: replay a saved token, autofill stored credentials, resume a remembered provider, open a pushed URL, save a fresh token, and act on the result. It renders no sign-in UI of its own, which also means the two sides never race to present competing screens: a version 2 client either sends a message promptly or hands off promptly. The server still allows for the client that does neither — it may be prompting the player for an unsaved password, version 1 style — by waiting a generous window before its interactive screen takes over (see Char.Login.Credentials).

The resulting first-connection experience on a capable client: the game's login screen appears immediately; the player picks a provider there; the browser opens by itself; they authorize; the server logs them in and mints a token. Every connection after that is instant and screenless via Char.Login.Reconnect.

Flows

Password credentials

Password credentials sign-in sequence.
  1. Client connects and sends: Core.Supports.Set ["Char.Login 2", ...]
  2. Server responds with: Char.Login.Default {"version": 2, "type": ["password-credentials"]}
  3. Client sends: Char.Login.Credentials {"account": "username", "password": "password", "version": 2}
  4. Server validates and replies with: Char.Login.Result {"success": true}

A version 1 client uses this same flow without the version fields.

Browser sign-in (server-owned screen)

Browser sign-in on the server-owned screen.
  1. Client connects and sends: Core.Supports.Set ["Char.Login 2", ...]
  2. Server responds with: Char.Login.Default {"version": 2, "type": ["oauth", "password-credentials"]}
  3. Holding no saved token or credentials, the client hands off immediately: Char.Login.Credentials {}
  4. The server begins its normal interactive login at once, printing its own sign-in menu.
  5. The player picks a provider on the game's screen — types a number or clicks a link.
  6. The server pushes Char.Login.URL {"url": "https://example.com/oauth/start?provider=discord&nonce=abc123", "nonce": "abc123", "provider": "discord"} and also prints the URL in its own text.
  7. The player has sent input this connection (their menu choice), so the client auto-opens the URL; the player signs in and consents at the provider.
  8. The provider redirects back to the server, which exchanges the authorization code server-side and authenticates the session. Nothing secret crossed the GMCP connection.
  9. The server mints a reconnect credential: Char.Login.Token {"account": "myaccount:mycharacter", "token": "opaque-reconnect-token"}; the client saves it.
  10. Server confirms with: Char.Login.Result {"success": true}

Later reconnect (password-less, screenless)

Password-less reconnect using a saved token.
  1. Client connects and sends: Core.Supports.Set ["Char.Login 2", ...]
  2. Server responds with: Char.Login.Default {"version": 2, "type": ["oauth", "password-credentials"]}
  3. Having a saved token (and no stored credentials, which take precedence), the client sends: Char.Login.Reconnect {"account": "myaccount:mycharacter", "token": "opaque-reconnect-token", "version": 2}
  4. On success: the server connects the player — directly into the character the token names, when the account still owns it — may rotate the token with a fresh Char.Login.Token (the client overwrites its copy), then confirms with Char.Login.Result {"success": true}.
  5. On failure: the server sends Char.Login.Result {"success": false, "message": "Reconnect token expired"}; the client drops only the dead token, keeps the account and provider, and continues with the resume flow below (or the interactive hand-off when no provider is remembered).

Resume (browser sign-in, no menu)

When the reconnect token has expired or been revoked but the client still remembers the account and provider from an earlier sign-in, one browser visit — and nothing else — re-establishes the session:

Resuming the remembered provider's browser sign-in — no password, no provider menu.
  1. Client connects and sends: Core.Supports.Set ["Char.Login 2", ...]
  2. Server responds with: Char.Login.Default {"version": 2, "type": ["oauth", "password-credentials"]}
  3. Holding no usable token but remembering the account and provider, the client sends the resume form: Char.Login.Credentials {"account": "myaccount:mycharacter", "provider": "discord", "version": 2}
  4. The server starts that provider's browser flow directly and pushes Char.Login.URL {"url": "...", "nonce": "...", "provider": "discord"} — no provider menu appears.
  5. The player authorizes in the browser (typically one click — the provider session usually still exists); the provider redirects back to the server, which validates the account–provider binding.
  6. The server connects the player — directly into the character named by the account hint, when the account still owns it — mints a fresh per-device Char.Login.Token, and confirms with Char.Login.Result {"success": true}.

Token lifecycle

  • Rotation (single-use): on each successful reconnect the server may issue a fresh token and invalidate the one just used, shrinking the replay window of any captured token to near zero.
  • One token per device, several per account: a player signs in from more than one machine (and more than one client). A server SHOULD hold a small set of concurrent tokens per account — each minted for one client install, rotated in place, with its own lifetime — rather than a single slot that every new device silently invalidates, which otherwise forces a browser re-pairing on every device switch. The token itself is the device's identity: opaque, anonymous, and individually revocable. Servers SHOULD NOT fingerprint devices to tell them apart — the token already does that without collecting anything about the player's hardware.
  • Revocation: the server may revoke one token or all of them at any time — on password change, provider unlink, or an explicit sign-out-everywhere. The next reconnect fails cleanly and the client falls back to the resume form or the hand-off.
  • No password fallback: a rejected token is always reported via Char.Login.Result {"success": false}, never routed into a password or password-creation prompt.

Security and transport

  • The reconnect token is an opaque, server-owned bearer secret carried in its own message, never conflated with the game password. Over plain telnet:// it travels in cleartext like a password — a long-standing MUD reality; single-use rotation, a sensible TTL, and a login-only scope limit the risk. A server may choose to issue and accept tokens only over telnets://.
  • In the browser sign-in, only the URL and an opaque nonce cross the GMCP connection; the authorization code is exchanged out of band over HTTPS. The flow is therefore safe over plain telnet.
  • Clients MUST validate that any Char.Login.URL is http/https before opening it, and SHOULD gate auto-opening an unprompted URL on evidence of user intent (input sent this connection).
  • Clients SHOULD treat credentials, tokens, and any buffers holding them as secrets: clear them from memory once sent or stored.

Guidance for servers

  • Own the interactive screen, and wait well. Keep your textual sign-in first-class — most connecting clients have nothing else, and it is the natural home for provider choice, character creation, and "remember me" consent. Treat Char.Login.Credentials {} as "proceed interactively now" and begin your normal login immediately — presenting the interactive welcome exactly once however the hand-off arrives. But treat silence differently: a client that advertised Char.Login and has said nothing may be prompting its player for an unsaved password (valid since version 1), so give it a generous window — silently, since the player is at their client's prompt — before the interactive screen takes over. A GMCP client that never advertised Char.Login needs no wait at all.
  • Dispatch client requests in your advertised order. When more than one Char.Login request could apply, process them in the same preference order you advertised in type; a reconnect token is not one of the advertised methods and is verified ahead of them. Let incomplete credentials (a missing account or password) fall through to the next method rather than dead-ending the sign-in, scrubbing any partial secret either way.
  • Push the URL when the browser step arrives, with the provider label, and still print it in your own text so every other client can click or copy it.
  • Mint and manage tokens deliberately. Issue Char.Login.Token after a successful sign-in regardless of which screen drove it; prefer single-use rotation; set a sensible TTL; revoke on password change or provider unlink. Hold a small set of tokens per account — one per client install, rotated in place — so a player's other devices stay signed in, and never fingerprint devices to tell them apart: the token already is the device's identity. Mint the account in its character-qualified form (myaccount:mycharacter) so each device's reconnect goes straight into the character it last played, skipping the roster. Gather any "remember me" consent in your own flow — clients treat token receipt as your instruction to save.
  • Report the negotiated version in Char.Login.Default and gate version 2 messages on the client having negotiated version 2.
  • Send Char.Login.Result as the terminal message of every flow. If a client echoes a version you cannot honour, reject with {"success": false} rather than misparse the payload.

Guidance for clients

  • A minimal client is fully conformant with: handle Char.Login.Result; open Char.Login.URL (with the safety checks above); send Char.Login.Credentials {} when you have nothing stored. Saving Char.Login.Token and replaying it with Char.Login.Reconnect is the single highest-value addition — it makes every later connect instant.
  • Decide promptly and deterministically on Char.Login.Default, in this order: (1) when the game offers password-credentials and the profile stores a complete character name and password, autofill Char.Login.Credentials — storing them is the player's explicit choice of sign-in method, so it is the default and no separate setting is needed; (2) otherwise, replay a saved reconnect token; (3) otherwise, with an account and provider remembered from an earlier browser sign-in, send the resume form of Char.Login.Credentials; (4) otherwise, hand off with Char.Login.Credentials {} and let the game's screen drive. Never let a partial credential (a password without a name, or vice versa) block the later rungs. Stored credentials outrank a saved token because they name the exact character the player wants to play, whereas the token names whatever account last signed in; the token serves the password-less profile.
  • Persist reconnect tokens with their provider: on Char.Login.Token, save the account, the token, and the provider learned from Char.Login.URL, together, in protected storage (a keychain or equivalent — never a plaintext settings file), and overwrite on rotation. On a rejected Char.Login.Reconnect, first re-read the store: if the saved token no longer matches the one just sent, another running instance of the client sharing the store rotated it — replay the fresh token instead of discarding. If it does match, the token is truly dead: discard the token but keep the account and provider, so the next attempt can use the resume form instead of sending the player back to a provider menu. A local "forget this sign-in" control clears all of it.
  • Echo the version you are acting on; treat an absent server version as version 1.

Appendix: client-driven OAuth (advanced, optional)

A server that is itself an OpenID Provider may additionally let an OIDC-capable client run the authorization request end to end — useful when the client can offer a smoother native handoff than the brokered flow. This appendix is entirely optional in both directions: servers that broker external providers omit it, and clients that do not implement it ignore its fields and use the flows above.

Additional Char.Login.Default fields (sent only over an encrypted transport, telnets:// / TLS):

  • location (string): the server's OpenID Connect Discovery document (.well-known/openid-configuration).
  • client_id (string): a public OAuth client identifier; PKCE protects it, no secret involved.
  • scopes (array of strings, optional): scopes to request; defaults to ["openid"].
  • nonce (boolean, optional): when true, the client SHOULD include an OpenID Connect nonce in the authorization request.

Char.Login.AuthCode (client → server): after capturing the authorization code at its loopback redirect, the client submits it; the server performs the token exchange and validation, so provider tokens never reach the client.

  • code (string, required): the captured authorization code.
  • code_verifier (string, required): the PKCE verifier matching the code_challenge sent in the authorization request.
  • redirect_uri (string, required): the redirect URI used in the authorization request, replayed verbatim in the token exchange.
  • version (integer, optional): the negotiated version.
Client-driven OAuth against the game's own OpenID Provider.

Flow: the client fetches location to find the authorization_endpoint; generates a PKCE verifier with an S256 challenge (REQUIRED) and a random state; starts a loopback listener (RFC 8252; RECOMMENDED) as the redirect URI; opens the browser to the authorization endpoint; verifies state on the redirect and captures code; sends Char.Login.AuthCode. The server exchanges the code at its token_endpoint, validates, authenticates the session, may issue Char.Login.Token, and confirms with Char.Login.Result.

Transport requirement: Char.Login.AuthCode carries the authorization code and PKCE verifier together, which on a cleartext connection would let an eavesdropper redeem the code at the provider. A client MUST NOT send it — and a server MUST NOT accept it — over an unencrypted connection, and a server MUST advertise location/client_id only over TLS so no client ever begins the flow in the clear.

Fit: only genuinely public OIDC providers qualify (a self-hosted OP, or the game itself). External providers with no discovery document, confidential secrets, or non-OAuth identity systems are reachable only through the server's own brokered screen, which the base flows above already cover.

Reference implementation

A working server-side implementation exists in StickMUD: the interactive hand-off, the pushed Char.Login.URL against real providers (Discord, GitHub, Google, Microsoft), Char.Login.Token issuance from the game's own screen, Char.Login.Reconnect with rotation, and revocation on password change, provider unlink, and sign-out-everywhere. Client-side, Mudlet implements version 2 including the reconnect flow and the client-driven appendix.

Implementation considerations

  • Implement this extension according to the GMCP protocol specifications.
  • Messages must be well-formed JSON objects.
  • Servers must validate incoming authentication information securely.
  • Clients should handle errors and unexpected server responses gracefully.

Conclusion

This GMCP extension keeps the game in charge of its own sign-in screen while giving clients the hooks to automate around it — culminating in password-less, screenless reconnects. The result is a floor low enough for every client and a ceiling high enough for OAuth-based sign-in, without burdening either side with the other's presentation.

Note: This document describes version 2 of the Char.Login extension. Version 1 (password-credentials only) remains valid and is what a server offers to clients that negotiate Char.Login 1. This is a draft proposal, and further community input and refinement may be necessary before finalizing the specification.