Mudlet Mapper Script
Auto-populate your Mudlet map as you explore Mystic
Mystic supports GMCP (Generic MUD Communication Protocol), which sends structured room data to your client as you move. This script reads that data and builds a live map inside Mudlet's built-in mapper. Rooms are color-coded by terrain, exits are linked automatically, and clicking any room on the map speedwalks you there.
Requirements
- Mudlet 4.x or newer
- Connect via telnet:
mysticmud.comport3000
Installation
- Copy the script below (use the Copy button).
- In Mudlet, open the Script Editor (the
<>button in the toolbar). - Click Add Item and choose Script (not Trigger or Alias).
- Give it a name like Mystic Mapper, paste the script, then click Save Item and Save Profile.
- If you have Mudlet's built-in generic-mapper package installed, uninstall it first:
Package Manager → generic-mapper → Uninstall - Disconnect and reconnect to Mystic.
Usage
- The map auto-populates as you walk. Open it with View → Map.
- Click any room on the map to speedwalk there automatically.
- Rooms are color-coded by terrain type (forest = green, water = blue, underground = grey, etc.).
To reset the map and start fresh:
In the Mudlet Lua console, run:
Then reconnect.
In the Mudlet Lua console, run:
lua deleteMap(); roomCount = 0Then reconnect.
Script
-- ==========================================================================
-- GMCP Mapper Script for Mystic MUD (Mudlet)
--
-- Setup:
-- 1. Open Mudlet Script Editor (the <> button)
-- 2. Create a new Script (not Trigger, not Alias)
-- 3. Paste this entire file
-- 4. Save and activate
-- 5. Disable Mudlet's built-in "generic-mapper" package if installed
-- (Package Manager > generic-mapper > Uninstall)
-- 6. Disconnect and reconnect
--
-- Usage:
-- After connecting, the mapper auto-populates as you walk.
-- Open the map window: View > Map
-- Click any room on the map to speedwalk there.
--
-- To reset the map (start fresh):
-- lua deleteMap(); roomCount = 0
-- Then reconnect.
-- ==========================================================================
mudlet = mudlet or {}; mudlet.mapper_script = true
-- -------------------------------------------------------------------------
-- Terrain colors (custom env IDs > 272 to avoid Mudlet built-in conflicts)
-- -------------------------------------------------------------------------
local terrain_envs = {
town = 300, path = 301, forest = 302, mountain = 303,
water = 304, swamp = 305, desert = 306, underground = 307,
inside = 308, plain = 309, hills = 310, jungle = 311,
shore = 312, shallows = 313, underwater = 314, canyon = 315,
valley = 316, ice = 317, mud = 318, rocky = 319,
scrub = 320, air = 321, unknown = 322,
}
setCustomEnvColor(300, 200, 200, 200, 255) -- town: light grey
setCustomEnvColor(301, 169, 169, 169, 255) -- path: grey
setCustomEnvColor(302, 0, 128, 0, 255) -- forest: green
setCustomEnvColor(303, 139, 69, 19, 255) -- mountain: brown
setCustomEnvColor(304, 0, 0, 255, 255) -- water: blue
setCustomEnvColor(305, 107, 142, 35, 255) -- swamp: olive
setCustomEnvColor(306, 210, 180, 140, 255) -- desert: tan
setCustomEnvColor(307, 105, 105, 105, 255) -- underground: dim grey
setCustomEnvColor(308, 255, 255, 224, 255) -- inside: light yellow
setCustomEnvColor(309, 144, 238, 144, 255) -- plain: light green
setCustomEnvColor(310, 154, 205, 50, 255) -- hills: yellow green
setCustomEnvColor(311, 0, 100, 0, 255) -- jungle: dark green
setCustomEnvColor(312, 238, 214, 175, 255) -- shore: sandy
setCustomEnvColor(313, 135, 206, 235, 255) -- shallows: sky blue
setCustomEnvColor(314, 0, 0, 139, 255) -- underwater: dark blue
setCustomEnvColor(315, 160, 82, 45, 255) -- canyon: sienna
setCustomEnvColor(316, 124, 252, 0, 255) -- valley: lawn green
setCustomEnvColor(317, 176, 224, 230, 255) -- ice: powder blue
setCustomEnvColor(318, 101, 67, 33, 255) -- mud: dark brown
setCustomEnvColor(319, 128, 128, 128, 255) -- rocky: grey
setCustomEnvColor(320, 189, 183, 107, 255) -- scrub: khaki
setCustomEnvColor(321, 230, 230, 250, 255) -- air: lavender
setCustomEnvColor(322, 220, 220, 220, 255) -- unknown: light grey
-- -------------------------------------------------------------------------
-- Direction helpers
-- -------------------------------------------------------------------------
local dir_offsets = {
north = { 0, 1, 0},
south = { 0, -1, 0},
east = { 1, 0, 0},
west = {-1, 0, 0},
northeast = { 1, 1, 0},
northwest = {-1, 1, 0},
southeast = { 1, -1, 0},
southwest = {-1, -1, 0},
up = { 0, 0, 1},
down = { 0, 0, -1},
}
local dir_to_num = {
north = 1, northeast = 2, northwest = 3,
east = 4, west = 5,
south = 6, southeast = 7, southwest = 8,
up = 9, down = 10,
}
-- -------------------------------------------------------------------------
-- State
-- -------------------------------------------------------------------------
local prevRoomID = nil
local prevRoomHash = nil
prevExitsGMCP = prevExitsGMCP or {}
roomCount = roomCount or 0
-- -------------------------------------------------------------------------
-- GMCP negotiation: request packages when Core.Hello arrives
-- -------------------------------------------------------------------------
function onCoreHello()
sendGMCP([[Core.Supports.Set ["Char 1", "Room 1", "Comm 1", "Group 1"] ]])
end
-- -------------------------------------------------------------------------
-- Room mapper: creates rooms, sets coordinates, links exits
-- -------------------------------------------------------------------------
function onGMCPRoomInfo()
if not gmcp.Room or not gmcp.Room.Info then return end
local info = gmcp.Room.Info
local hash = info.num
-- Strip exit list from name: "Town Square (n, s)" -> "Town Square"
local cleanName = string.gsub(info.name, "%s*%b()$", "")
-- Look up or create the room
local roomID = getRoomIDbyHash(hash)
local isNew = false
if not roomID or roomID == -1 then
isNew = true
roomCount = roomCount + 1
roomID = roomCount
while roomExists(roomID) do
roomCount = roomCount + 1
roomID = roomCount
end
addRoom(roomID)
setRoomIDbyHash(roomID, hash)
end
setRoomName(roomID, cleanName)
-- Area management
if info.area then
local areaTable = getAreaTable() or {}
local areaID = areaTable[info.area]
if not areaID then
areaID = addAreaName(info.area)
end
setRoomArea(roomID, areaID)
end
-- Coordinates: only set for NEW rooms, based on direction from previous
if isNew and prevRoomID then
local moved_dir = nil
-- Check which direction from the previous room leads here
if prevRoomHash then
local prevExits = getRoomExits(prevRoomID) or {}
for dir, destID in pairs(prevExits) do
if destID == roomID then
moved_dir = dir
break
end
end
-- If not linked yet, check GMCP exits of the previous room
if not moved_dir then
for dir, destPath in pairs(prevExitsGMCP or {}) do
local cleanPath = destPath:gsub("^/", "")
if cleanPath == hash then
moved_dir = dir
break
end
end
end
end
local px, py, pz = getRoomCoordinates(prevRoomID)
local offset = moved_dir and dir_offsets[moved_dir]
if offset then
setRoomCoordinates(roomID, px + offset[1], py + offset[2], px and pz + offset[3] or offset[3])
else
-- Unknown direction: place slightly offset to avoid overlap
setRoomCoordinates(roomID, px + 1, py, pz)
end
elseif isNew then
setRoomCoordinates(roomID, 0, 0, 0)
end
-- Terrain color
local envID = terrain_envs[info.terrain] or 322
setRoomEnv(roomID, envID)
-- Set exits: link to known rooms, stub for unknown
local exits = info.exits or {}
for dir, destPath in pairs(exits) do
local dirNum = dir_to_num[dir]
if dirNum then
local cleanPath = destPath:gsub("^/", "")
local destID = getRoomIDbyHash(cleanPath)
if destID and destID ~= -1 then
setExit(roomID, destID, dirNum)
else
setExitStub(roomID, dirNum, true)
end
end
end
-- Save state for next move
prevExitsGMCP = exits
prevRoomID = roomID
prevRoomHash = hash
centerview(roomID)
end
-- -------------------------------------------------------------------------
-- Speedwalk: clicking a room on the map walks there
-- -------------------------------------------------------------------------
function doSpeedWalk()
for _, dir in ipairs(speedWalkDir) do
send(dir)
end
end
-- -------------------------------------------------------------------------
-- Placeholder handlers for other GMCP packages
-- (These keep Mudlet's gmcp table populated)
-- -------------------------------------------------------------------------
function onGMCPReceived() end
-- -------------------------------------------------------------------------
-- Register all event handlers
-- -------------------------------------------------------------------------
registerAnonymousEventHandler("gmcp.Core.Hello", "onCoreHello")
registerAnonymousEventHandler("gmcp.Room.Info", "onGMCPRoomInfo")
registerAnonymousEventHandler("gmcp.Char.Name", "onGMCPReceived")
registerAnonymousEventHandler("gmcp.Char.Vitals", "onGMCPReceived")
registerAnonymousEventHandler("gmcp.Char.Status", "onGMCPReceived")
registerAnonymousEventHandler("gmcp.Char.Afflictions", "onGMCPReceived")
registerAnonymousEventHandler("gmcp.Char.Items", "onGMCPReceived")
registerAnonymousEventHandler("gmcp.Comm.Channel", "onGMCPReceived")
registerAnonymousEventHandler("gmcp.Group.Info", "onGMCPReceived")
Questions? Ask in-game or on Discord.