Converting zMUD Commands to Mudlet

Converting zMUD Commands to Mudlet

Jun 02, 2026

Converting from one MUD client to another can be quite challenging. zMUD uses a different scripting language (a custom one called zScript) while Mudlet uses Lua. In this post I take zMUD commands from the zMUD manual and convert them to Mudlet aliases, scripts and functions to help you better understand Mudlet.

Note: Each zMUD command is a link to the online zMUD manual.

Loop Structures

#number repeat following text number times

An often used command to repeat the same command over and over.

You can use {i} to insert the number in the command (e.g., #3 get all {i}.corpse will send get all 1.corpse, get all 2.corpse, etc.. to the game). There is no need to use the % symbol to designate it as a variable like in zMUD.

Alias: ^#(\d+)\s(.*)$
Script:

function repeatCommand(amount, command)
	for i = 1, amount do
		send(f(command))
	end
end

repeatCommand(matches[2], matches[3])

REPEAT repeat commands a given number of times

This is the same as the #number command above, just more verbose.

You can use {i} to insert the number in the command (e.g., #REPEAT 3 get all {i}.corpse will send get all 1.corpse, get all 2.corpse, etc.. to the game). There is no need to use the % symbol to designate it as a variable like in zMUD.

Alias: ^#REPEAT\s(\d+)\s(.*)$
Script:

function repeatCommand(amount, command)
	for i = 1, amount do
		send(command)
	end
end

repeatCommand(matches[2], matches[3])

LOOP execute command several times in a loop.

A bit different from #repeat, as you can use ranges and insert a number variable. For this we need to check the arguments to see what format they are in firstly.

You can use {i} to insert the number in the command (e.g., #LOOP 3 get all {i}.corpse will send get all 1.corpse, get all 2.corpse, etc.. to the game). There is no need to use the % symbol to designate it as a variable like in zMUD. Also, the embracing {} curly brackets around the command are optional.

Alias: ^#LOOP\s(\S+)\s(.*)$
Script:

function loop(arg, command)

  -- strip any embracing curly braces
  command = command:gsub("^%{(.*)%}$", "%1")

  -- i.e., #LOOP 1,4 {get coins {i}.corpse}
  -- i.e., #LOOP 1,4 get coins {i}.corpse
  if arg:match("%d+,%d+") then
    local from = arg:match("%d+\,"):gsub(",", "")
    local to = arg:match("\,%d+"):gsub(",", "")

    for i = from, to do
      send(f(command))
    end
    
    return
  end
  
  -- i.e, #loop @num eat bread
  if arg:match("\@%w") then
    arg = arg:gsub("@", "")
    if _G[arg] and tonumber(_G[arg]) then
      arg = tonumber(_G[arg])    
    else
      print("@variable does not exist or is not a number")
      return
    end
  end
  
  -- i.e., #loop 3 north
	for i = 1, arg do
		send(f(command))
	end
  
end

loop(matches[2], matches[3])

FORALL loop through a string list and execute command for each item

#FORALL uses %i for replacement, we will continue to use {i} instead.

e.g., #FORALL sword|shield|armor repair {i} will send repair sword, repair shield, repair armor

Alias: ^#FORALL\s(\w+(?:\|\w+)*)\s(.+)$

Script:

-- e.g., #FORALL sword|shield|armor repair {i}
function forAll(commands, action)  
	local commands = commands:split("|")
	for _,i in ipairs(commands) do
    	send(f(action))
  	end
end

forAll(matches[2], matches[3])

UNTIL execute commands until expression is true

WARNING: while and repeat until loops are not recommended in Mudlet! If you mess up and the value is never true then Mudlet will continue looping forever causing it to hang (you asked it to do that!). You can always find another way to write it in a for loop, or use coroutines if necessary.

local A = 10
repeat
    print(A)       -- equivalent to #SHOW @A
    A = A - 1      -- equivalent to #ADD A -1
until A == 0       -- loop stops when the expression is true

WHILE execute command while expression is true

WARNING: while and repeat until loops are not recommended in Mudlet! If you mess up and the value is never true then Mudlet will continue looping forever causing it to hang (you asked it to do that!). You can always find another way to write it in a for loop, or use coroutines if necessary.


local A = 10
while A ~= 0 do        -- zScript "<>" means "not equal", Lua uses "~="
    print(A)           -- equivalent to #SHOW @A
    A = A - 1          -- equivalent to #ADD A -1
end

LOOPDB loops through key values in a database record

Databases are an advanced subject. I will cover them in another post.

LMAP loop through rooms on the map

Using a speedwalk (e.g., 2n3w), perform a command along every step of the way.

local function getWalk(dirString)
  dirString = dirString:lower()
  local walklist = {}
  local long_dir = {north = 'n', south = 's', east = 'e', west = 'w', up = 'u', down = 'd'}
  
  for k,v in pairs(long_dir) do
    dirString = dirString:gsub(k,v)
  end
  
  for count, direction in string.gmatch(dirString, "([0-9]*)([neswudio][ewnu]?t?)") do
    count = (count == "" and 1 or count)
    for i = 1, count do
        walklist[#walklist + 1] = direction
        print(direction)
    end
  end
  
  return walklist
end

function walkAction(speedwalk, command)

  print(speedwalk, command)
  
  local path = getWalk(speedwalk)
  
  for _, dir in ipairs(path) do
    sendAll(dir, command)
  end
  
end

walkAction(matches[2], matches[3])

ABORT abort further parsing of the current loop or program block

Abort is akin to return in Lua.

-- aborts immediately after first print
for i = 1, 10 do
	print(i)
	return
end

-- output:
-- 1

-- aborts when i == 5, i.e., only iterates up to 5
for i = 1, 10 do
	if i == 5 then
		return
	end
end

-- output:
-- 1
-- 2
-- 3
-- 4
-- 5

Conditionals

IF perform a conditional test

It's the same as the if/else statement in Lua.

The following examples are from the zMUD manual converted to Mudlet.

-- #IF @autosplit {split @gold}
-- If the @autosplit variable is non-zero, then the value of @gold is expanded, the string split is sent to the MUD followed by the value of @gold.
if autosplit ~= 0 then
	send(f"split {gold}")
end

-- #IF (@gold < 100000) {emote is poor} {emote is RICH!}
-- If the value of the @gold variable is less than 100000, then the string emote is poor is sent to the MUD, otherwise the string emote is RICH! is sent to the MUD.
if gold < 100000 then
	send("emote is poor")
else
	send("emote is RICH!")
end
 
-- #IF (@line =~ "You receive (%d) coins") {split %1}
-- If the value of the variable @line matches the pattern You receive %d coins, then the number of coins matched is stored in the %1 parameter, and the string split is sent to the MUD, followed by the parameter. Note the nested quotation marks needed to properly parse this command.

-- Zooka NOTE: Mudlet already has a global 'line' variable which holds the last line received from the Mud.  I will use 'str' here instead just so we don't confuse the two.
-- Usually you would use a trigger, but we can also match using Lua's built in string matching.
local str = 'You receive 42 coins'  -- this is your input string
-- Capture the number of coins using Lua pattern matching (not regex!)
local coins = str:match("You receive (%d+) coins")

if coins then
    send(f"split {coins}")
end

CASE select a command from a list

-- simulate #CASE
function case(index, commands)
    local n = #commands -- get the size of the command table
	-- safety checks
    if n == 0 or index == nil or index < 1 then return nil end

    -- Wrap-around: e.g., index=5 with n=4 then index=1
    local pos = ((index - 1) % n) + 1
    return commands[pos]
end

-- #CASE 2 {first command} {second command} {third command}
-- sends the string second command to the MUD
local cmd = case(2, { "first command", "second command", "third command" })
if cmd then send(cmd) end -- sends "second command" to Mud

-- #CASE @joincmd {join} {rescue}
-- if the variable @joincmd is 1 (or 3,5,7...) the string join is returned, otherwise the string rescue is returned.
local joincmd = 3  -- this is your @joincmd variable
local cmd = case(joincmd, { "join", "rescue" })
if cmd then send(cmd) end

-- #CASE %random {Hello} {Hi there} {Hiya} {Hi}
-- returns a random string from the given list to the MUD.
local greetings = { "Hello", "Hi there", "Hiya", "Hi" }
local index = math.random(1, #greetings)
local cmd = case(index, greetings)
if cmd then send(cmd) end

Create/modify settings items

ACTION create or display a trigger action

TRIGGER create or display a trigger action

While it's recommended to create triggers visually using the Script Editor, you can create them programmatically if desired. So also temporary triggers (or zMUDs #TEMP). Use the appropriate function depending on your intended use case;

The following examples are from the zMUD manual converted to Mudlet.

-- #TRIG {chats} {#COLOR red}
-- whenever a line containing the word 'chat' is received, the color of the line is changed to red.
permSubstringTrigger("Chat Highlighter", "", {"chat"}, [[selectString(line, 1) bg("red") resetFormat()]])

-- #TRIG {^You get (%d) coins} {split %1} autosplit
-- Whenever you see a line like 'You get [number] coins' the number of coins is stored in the %1 parameter.

-- Zooka NOTE: Mudlet uses the matches table variable instead of %1, etc...
permRegexTrigger("Autosplit", "", {"^You get (\d+) coins"}, [[send(f"split {matches[2]}")]])

-- #TRIG {^~[&hp/&{maxhp}hp &mana/&{maxmana}ma~]} {#IF (@hp < @maxhp/10) {cast 'heal'}} "" "prompt"

-- Zooka NOTE: this is akin to using named matches in regex.
permRegexTrigger("Prompt match", "" {"^\[(?<hp>\d+)\/(?<maxhp>\d+)hp (?<mana>\d+)\/(?<maxmana>\d+)mn\]"}, [[if tonumber(matches.hp) < tonumber(matches.maxhp)/10 then send("cast 'heal'") end]])

ALARM create an alarm trigger

Recreating the ALARM function as implemented in zMUD here would take up quite a lot of room. I recommend adding the cron package (written by me) which allows you schedule jobs from 1 minute out to a year away.

For simple alarms, called timers in Mudlet, here are some examples using temporary timers and permanent timers (one's that last over Mudlet restarts and are visible in the Script Editor).

-- #ALARM +5 {save}
-- Executes the "save" command in 5 seconds. This is a one-time alarm that deletes itself once it has executed.
tempTimer(5, function() send("save") end)

-- or to keep repeating every 5 seconds
tempTimer(5, function() send("save") end, true)

-- or use a permTimer to save over Mudlet restarts
permTimer("save timer", "", 5, [[send("save")]])

TEMP create a temporary trigger

Just as with permanent triggers, you specify the type of temporary trigger you want. Temporary triggers do not last over Mudlet restarts. I recommended named triggers though, which will be discussed in another article. A tempTrigger is equivalent to a substring trigger.

-- #TEMP {You failed your cast} {cast @lastspell}
-- Sets up a trigger that will execute once and then delete itself.
local lastspell = "fireball" -- for testing
tempTrigger("You failed your cast", function() send(f"cast {lastspell}") end, 1)

ONINPUT create a command input trigger

This concept is the basis of all Mudlet aliases. Mudlet parses your input and checks if it matches, then runs your code instead of sending the exact command you typed to the game. All aliases in Mudlet regex type. You can use the Script Editor to create them or create them programmatically (permanent) or temporarily.

-- #ONINPUT {^h$} {cast 'heal'}
-- Whenever the command 'h' is entered by itself on the command line, the command "cast heal" is sent to the MUD.
tempAlias("^h$", [[send("cast 'heal'")]])

-- or use a permAlias to save over Mudlet restarts
permAlias("heal alias", "", "^h$", [[send("cast 'heal'")]])

ALIAS create or display an alias

Mudlet makes no distinction between simple or regex type aliases as zMUD does. Refer to ONINPUT above for more information.

GALIAS create a global alias

zMUD uses a concept of global command which persist across different character windows. Mudlet does not use this concept, but except uses a module system to share scripts between profiles. There is no equivalent of GALIAS in Mudlet.

RECORD record an alias

As per the zMUD manual.

Alias: ^#RECORD·?(\w+)?$
Script:

-- #RECORD starts a recording
-- #RECORD views an in progress recording
-- #RECORD <name> save an in progress recording to the alias <name> (you don't need the angle brackets, just a keyword
-- #RECORD stop stops a recording (also; cancel or off)
if matches[2] and not recordingAlias then
  print("Recording has not started.  Start recording with '#RECORD'.")
  return
end

if not recordingAlias then
  recordingAlias = tempAlias(".+", function() recordCommand(matches[1]) end)
  recordingCmds = ""
  print("Recording started.")
end

function recordCommand(cmd)
  if cmd == "#RECORD" then
    if recordingCmds then
      print("Current recording:")
      display(recordingCmds)
    end
    
    return
  end
  
  local arg = cmd:match(".+", 9)
  if arg then
    if arg == "stop" or arg == "off" or arg == "cancel" then
      print("Recording cancelled.")
      killAlias(recordingAlias)
      recordingAlias = nil
      recordingCmds = nil
      return
    end
          
    if recordingCmds and string.len(recordingCmds) > 0 then
      permAlias(arg, "", f"^{arg}$", f[[sendAll({recordingCmds})]])
      print("Recording saved.")
      killAlias(recordingAlias)
      recordingAlias = nil
      recordingCmds = nil
    else
      print("Nothing has been recorded.  Start recording with '#RECORD'.")
      killAlias(recordingAlias)
      recordingAlias = nil
      recordingCmds = nil         
    end
    
    return
  end
  
  send(cmd)
  recordingCmds = f"{recordingCmds}, \"{cmd}\""
  recordingCmds = recordingCmds:gsub("^, ", "")
end

PATH save or display the current path

Much the same as the above #RECORD function, but works only on paths. There is already a handy package for this called speedwalkRecorder, so I won't repeat it here.

VARIABLE assign a value to a variable

Variables are easily assigned in Lua (Mudlet's scripting language). Local variables which only work within a function, script, or trigger, for example, have the keyword local in front.

local coins = 1000
local myName = "Zooka"

GVARIABLE assign a value to a global variable

Simply drop the local keyword and now all variables can be accessed across the entire profile.
It is recommended to stick to local variables where ever possible to prevent overriding something.

coins = 1000
myName = "Zooka"

FUNCTION create a user-defined function

Creates a function which can be called from somewhere else, helping with code readability and reuse.

The following examples are from the zMUD manual converted to Mudlet.

-- #FU fact {%if(%1<=1,1,%1*@fact(%eval(%1-1)))}
-- creates a user defined function to compute the factorial of a number
function fact(n)
	if n <= 1 then
        return 1
    else
    	return n * fact(n - 1)
	end
end

     
-- #EVAL @fact(5)
-- will display 120 given the above definition
print(fact(5))

-- Returns the product expression as a string, e.g., "5*4*3*2*1"
function factExpr(n)
    if n <= 1 then
        return "1"
    else
        return tostring(n) .. "*" .. fact_expr(n - 1)
    end
end
print(factExpr(5))

MATH perform complex math and expression parsing

Lua has a built in math library that is very comprehensive; https://www.lua.org/manual/5.1/manual.html#5.6

-- #MATH test (1+3)*4
-- assigns the value of '16' to the variable @test.
local test = (1+3)*4
 
-- #MATH test2 @test-4
-- if @test has the value of 16, the value of 12 is assigned to @test2
local test = 16
local test2 = test - 4
 
-- #ALIAS add {#MATH value %1+%2}
-- add 3 4
-- the value of 7 is assigned to the variable @value
Alias: ^add (\d+) (\d+)

function add(num1, num2)
	return num1 + num2
end

local value = add(matches[2], matches[3])
print(value)

ADD add a value to a variable

-- #AD moves 1
-- Add one to the @moves variable
local moves = moves + 1

-- #ACTION {You get (%d) coins} {#AD gold %1}
-- When you pick up some coins, add their value to the @gold variable.
Regex trigger: ^You get (\d+) coins

Script:

gold = gold + tonumber(matches[2])

BUTTON trigger a button

In Mudlet you wouldn't trigger a button press like in zMUD. You would call the function you assigned to the button. If you created a button in the GUI to heal you, make a script called heal() and assign it to the button. Then you can call the heal() function anywhere else you like e.g., right click menu's, aliases, triggers, etc.,

Button are recommended to be created via the Script Editor. You use the Geyser GUI framework as well, but this is quite advanced and left for another discussion.

GAUGE create a graphical gauge button

There are a few function to manipulate gauges from your scripts. First you create the gauge, then you assign to values. You might do this via a prompt trigger for your hit points and movement points.

-- #GAUGE hp "hp" @hp @maxhp (@maxhp/10) "blue" "red"
-- creates a nice hitpoint gauge.

-- createGauge([name of userwindow], name, width, height, Xpos, Ypos, gaugeText, colorName, orientation)
createGauge("healthBar", 300, 20, 30, 300, "HP", "green")
-- set it to half full (e.g., from a trigger)
setGauge("healthBar", 200, 400, "HP 50%")

KEY define a macro key

Key's can be created via the Script Editor (permanent and easier) or programmatically. Temporary keys do not last over Mudlet restarts.

The following examples are from the zMUD manual converted to Mudlet.

-- #KEY F1 {eat bread}
-- assign the eat bread command to the F1 key
tempKey(mudlet.key.F1, function() send("eat bread") end)

-- <ALT-D>={drink water}
-- assign the command 'drink water' to the ALT-D key
permKey("quench thirst", "", mudlet.keymodifier.Alt, mudlet.key.D, [[send("drink water")]])

STATUS set the definition of the status bar

Mudlet doesn't have a status bar. It is far more sophisticated than that! You can control all types of GUI elements to display information anywhere you like on the screen. But this is delving into GUI territory, which is left for another post.

The following examples are from the zMUD manual converted to Mudlet.

-- #ST {Gold: @gold Tank: @tank}
local gold = 100
local tank = "Zooka"

statusBar = Geyser.MiniConsole:new({
  name = "statusBar",
  x = "70%", y = "50%",
  autoWrap = true,
  color = "black",
  scrollBar = false,
  fontSize = 12,
  width = "30%", height = "50%",
})
statusBar:echo(f"Gold: {gold} Tank: {tank}")

STW set status window definition

As above with #STATUS.

TAB add word to tab completion list

Alias: ^#TAB (\w+)$

Script:

addCmdLineSuggestion(matches[2])

RENAME rename an alias, variable or path

Manipulating objects can be done via the Script Editor. There are no built-in functions for this in Mudlet. If you wish to rename temporary items, simply delete them and recreate them with the new name.

MENU add a speed menu item

Right-click mouse events are handled by the event system, which will be covered in a later post.

The following examples are from the zMUD manual converted to Mudlet.

-- add 'eat bread' to the right-click menu
addMouseEvent("eat bread", "eatBreadEvent")

function eatBread()
	send("eat bread")
end

registerNamedEventHandler("right-click", "eat bread example", "eatBreadEvent", "eatBread")

DIR add a direction setting

There is no Mudlet equivalent, but the mapper does support custom exits. Built-in directions (and reverse directions) include; n, ne, e, se, s, sw, w, nw, u, d, in, out.

EDIT edit a given setting

There is no Mudlet equivalent. Just navigate to the appropriate alias or trigger using the mouse.

Enjoy this post?

Buy Zooka a coffee

More from Zooka