Guides

MUD Scripting with Lua

Mudlet's Lua environment allows sophisticated MUD automation, but ad-hoc scripts become unmaintainable when MUD codebases update or when sharing across servers. This guide implements a modular architecture using namespace isolation, event-driven handlers, and automated migration layers to create portable script packages that survive codebase changes and distribute cleanly via Mudlet's module system.

90 minutes7 steps
MUD Scripting with Lua illustration
Placeholder illustration shown while custom artwork is being produced.
1

Audit Existing Triggers and Extract Domain Patterns

Before refactoring, export your current Mudlet profile triggers and aliases. Identify repeated regex patterns across multiple triggers (e.g., health prompts, room titles). Create a mapping document that categorizes patterns by MUD system type (combat, navigation, communication) to determine which should become shared library functions versus standalone triggers.

⚠ Common Pitfalls

  • Avoid copying color codes directly; Mudlet uses \x1b for ANSI escape sequences in regex
  • Do not export sensitive passwords stored in aliases; sanitize before committing to Git
2

Implement Namespace Isolation in Lua

Create a root table structure that prevents global namespace pollution. Define your package as a table named after your project (e.g., MyMudUtils) and nest all functions, state variables, and sub-modules within it. Use local references at the file scope for internal helpers that should not be exposed to Mudlet's global environment or other packages.

mymudutils.lua
-- mymudutils.lua
local MyMudUtils = MyMudUtils or {}
MyMudUtils._VERSION = "1.0.0"
MyMudUtils._AUTHOR = "muddev"

-- Private state
local internalCache = {}

-- Public API
function MyMudUtils.setup()
  internalCache.triggers = {}
end

return MyMudUtils

⚠ Common Pitfalls

  • Never use global variables without the package prefix; they persist across profile reloads and cause conflicts
  • Avoid naming tables 'triggers' or 'aliases' as these clash with Mudlet's internal tables
3

Build the Event Registration Core

Design a registration system that wraps Mudlet's registerAnonymousEventHandler to allow multiple listeners per event type. This decouples your triggers from Mudlet's internal event system, enabling you to unregister and re-register handlers programmatically during migrations without restarting the client.

events.lua
function MyMudUtils.registerEvent(eventName, handlerFunc)
  if not MyMudUtils.eventRegistry then
    MyMudUtils.eventRegistry = {}
  end
  
  local handlerId = registerAnonymousEventHandler(eventName, handlerFunc)
  table.insert(MyMudUtils.eventRegistry, {
    id = handlerId,
    event = eventName,
    active = true
  })
  return handlerId
end

⚠ Common Pitfalls

  • Failing to store handler IDs prevents cleanup during package updates
  • Event names in Mudlet are case-sensitive; 'sysDataSendRequest' differs from 'SysDataSendRequest'
4

Create a Centralized Pattern Registry

Move all regex patterns into a centralized table indexed by logical names (e.g., 'healthPrompt', 'roomExits'). This abstraction layer allows you to update patterns in one location when the MUD changes its prompt format, rather than hunting through individual triggers. Implement a pattern validator that tests regex compilation at load time.

patterns.lua
MyMudUtils.patterns = {
  healthPrompt = [[^(\d+)/(\d+)h (\d+)/(\d+)m]],
  roomExits = [[\[Exits: ([^\]]+)\]]]
}

function MyMudUtils.validatePatterns()
  for name, pattern in pairs(MyMudUtils.patterns) do
    local ok, err = pcall(string.match, "test", pattern)
    if not ok then
      echo("ERROR: Invalid pattern '" .. name .. "': " .. err .. "\n")
    end
  end
end

⚠ Common Pitfalls

  • Magic numbers in regex capture groups break when MUD adds color codes; use non-greedy wildcards .*? where possible
  • Lua patterns use % for escapes, not \; ensure consistency if mixing regex types
5

Implement Migration Handlers for Version Updates

Add a version check that runs on profile load. Compare the current package version against a stored version in Mudlet's save table. If versions differ, execute migration functions that update trigger patterns, rename saved variables, or restructure data formats to match the new codebase requirements without losing user configuration.

migrate.lua
function MyMudUtils.migrate()
  local currentVersion = getMudletHomeDir() .. "/MyMudUtils/version.txt"
  local lastVersion = io.exists(currentVersion) and io.open(currentVersion):read("*l") or "0.0.0"
  
  if lastVersion < "1.0.0" then
    -- Rename old variable format
    if myOldVariable then
      MyMudUtils.config.newVariable = myOldVariable
      myOldVariable = nil
    end
  end
  
  -- Write new version
  local f = io.open(currentVersion, "w")
  f:write(MyMudUtils._VERSION)
  f:close()
end

⚠ Common Pitfalls

  • Migration logic must handle nil values gracefully; never assume the variable exists from previous versions
  • Always backup user data before destructive migrations using copy() on tables
6

Package for Distribution with Metadata

Create the Mudlet module structure by organizing your Lua files, XML trigger definitions, and a config.lua into a directory. Generate a module metadata file that specifies dependencies, minimum Mudlet version, and installation instructions. Zip the directory with the exact structure Mudlet expects for drag-and-drop installation.

config.lua
-- config.lua
return {
  name = "MyMudUtils",
  version = "1.0.0",
  author = "muddev",
  description = "Modular utility package for MUD automation",
  min_mudlet_version = "4.14",
  dependencies = {},
  triggers = {
    "health_monitor.xml",
    "navigation_helper.xml"
  }
}

⚠ Common Pitfalls

  • XML files must use UTF-8 encoding without BOM; Mudlet fails to import with BOM present
  • Relative paths in require() statements break when users install to different directories; use getMudletHomeDir() for path construction
7

Debug Using Mudlet's Error Console and Echo Testing

Test the package by loading it in a clean Mudlet profile. Use the Debug console (Settings -> General -> Show errors in main window) to catch Lua syntax errors. Create a test harness that simulates MUD output using feedTriggers() to verify pattern matching without connecting to the server. Check that migrations run correctly by manually downgrading the version file and reloading.

test_harness.lua
-- test_harness.lua
function MyMudUtils.testPatterns()
  local testLine = "100/100h 50/50m"
  local h, hm, m, mm = testLine:match(MyMudUtils.patterns.healthPrompt)
  if h == "100" then
    echo("Health pattern: PASS\n")
  else
    echo("Health pattern: FAIL - got " .. tostring(h) .. "\n")
  end
end

feedTriggers("100/100h 50/50m\n")
MyMudUtils.testPatterns()

⚠ Common Pitfalls

  • feedTriggers() does not trigger Lua event handlers registered via registerAnonymousEventHandler; use raiseEvent() for those tests
  • Debug output in Mudlet appears below the main window; scroll down or use openUserWindow() to capture logs

What you built

This modular architecture separates concerns into isolated namespaces, centralizes pattern management, and provides migration pathways for long-term maintenance. By packaging as a Mudlet module with version metadata, you enable clean distribution across MUD communities while protecting user configurations from breaking changes. Test thoroughly in isolated profiles before distributing to production environments.