TinTin++ Setup
Audience: Player Status: ✅ Ready
Recipes for TinTin++ (tt++) — a fast, scriptable, terminal-based client that a lot of MUD players prefer. Unlike Mudlet, tt++ has no GUI: it drives everything from # commands in a terminal, and it ships a genuinely good built-in mapper. Everything below is illustrative — reach for the TinTin++ manual for exact syntax, since command details vary a little across tt++ versions.
How TinTin++ delivers GMCP
TinTin++ negotiates GMCP automatically when the server offers it (option 201). A received GMCP message raises a telnet-subnegotiation event, and you hook it with #event:
#event {IAC SB GMCP} {…}fires for any GMCP message —%0is the module (package) name,%1is its data.#event {IAC SB GMCP <Module>} {…}fires for one package —%1is its data.
TinTin++ hands you %1 already converted to its native table format — no JSON parsing needed. The idiomatic setup keeps every package in a variable named after the module:
#event {IAC SB GMCP} {#var {%0} {%1}}
After that, GMCP is just variables you index — ${Char.Vitals[hp]}, ${Room.Info[num]} — and you hook a specific package’s event to react when it changes:
#event {IAC SB GMCP Char.Vitals}
{
#var {v} {%1}
#showme {HP: ${v[hp]}/${v[maxhp]} MP: ${v[mana]}/${v[maxmana]}}
}
See the GMCP Reference for every package and its fields.
Content-defined keys: the demo world’s vitals are
hp/mana(sohp/maxhp/mana/maxmana); a different content pack uses its own resource refs.
Auto-open the login link
When you connect, the gate prints your one-click OAuth link and waits for you to sign in (Player Reference → Connecting). An #action (tt++’s trigger) can spot it and hand the URL to your OS’s browser opener:
#action {open this link in your browser} {#variable {telos_login} {1}}
#action {http%1}
{
#if {"$telos_login" == "1"}
{
#script {_} {xdg-open "http%1" > /dev/null 2>&1 &};
#variable {telos_login} {0}
}
}
The first action arms a flag on the prompt line; the second catches the following URL line and opens it. Swap xdg-open for your platform’s opener (open on macOS, start on Windows). The OAuth page closes itself after a few seconds and hands you back to the MUD.
Matching note: this keys off the gate’s login screen text, which is engine code, not content — so a world changing its content (rooms, mobs, even its message of the day) leaves the trigger working; only a fork that edits the auth code’s wording would need the pattern updated.
The built-in mapper
This is where tt++ shines: #map is a full automapper, no plugin required.
#map create 1000 // start a new map
#map goto <vnum> dig // move to a room, digging (creating) it if new
#map map 12 40 // draw a 12×40 map to the screen
#map write mymap.tin // save it
Rooms are created automatically as you walk, and #map flag nofollow explicitly supports GMCP-driven automapping. Drive it from the server’s Room.Info package — its num is a stable per-room id and it carries coord and exits:
#event {IAC SB GMCP Room.Info}
{
#var {room} {%1}
#map goto ${room[num]} dig
#map roomname ${room[name]}
}
Vitals in a split (ASCII bars)
tt++ has no floating gauges, but it does have #split, which carves off a fixed number of rows the scrollback never touches — the perfect home for a status line. Combine it with #draw ... bar, which paints a proportional ASCII bar into a rectangle, and refresh both on the Char.Vitals event.
Reserve two bottom rows once (top/bottom/left/right/input in rows and columns; 0 2 = no top region, a 2-row bottom region):
#split 0 2
Then draw an HP bar and an MP bar into those rows whenever vitals change. #draw [horizontal] bar <r1> <c1> <r2> <c2> {<cur>;<max>;<color>} fills the rectangle in proportion to cur/max; negative rows count up from the bottom of the screen, so -2/-1 land in the split regardless of terminal height:
#event {IAC SB GMCP Char.Vitals}
{
#var {v} {%1}
#draw horizontal bar -2 1 -2 30 {${v[hp]};${v[maxhp]};124}
#draw horizontal bar -1 1 -1 30 {${v[mana]};${v[maxmana]};021}
#draw -2 32 -2 79 {<088>HP ${v[hp]}/${v[maxhp]}}
#draw -1 32 -1 79 {<021>MP ${v[mana]}/${v[maxmana]}}
}
The two bar lines are the ASCII fill (color 124 red for HP, 021 blue for MP); the two plain #draw lines print the raw numbers beside each bar so you see the exact values update. Tune the column widths (30, 79) to your terminal, and drop the label lines if you only want the bars. See #split and #draw in the manual for the full option list. A no-split fallback is to just #showme the numbers whenever Char.Vitals changes.
Channels
The engine sends channel chatter both as text and, to GMCP clients, as Comm.Channel.Text ({channel, talker, text}) with the usable channel list in Comm.Channel.List — see GMCP Reference. You can hook it and, for example, colour or tag each channel:
#event {IAC SB GMCP Comm.Channel.Text}
{
#var {ch} {%1}
#showme {<138>[${ch[channel]}]<099> ${ch[talker]}: ${ch[text]}}
}
Placeholder — separate channel windows. tt++ is a single terminal; it has no dockable per-channel sub-windows like Mudlet’s miniconsoles. Practical options today: split the screen into regions with
#split, gag a channel from the main flow and mirror it into a#bufferor a log, or run tt++ inside a terminal multiplexer (tmux/screen) and pane it out. A first-class “one window per channel” recipe is not something the client provides.
Tab-completion
tt++ already tab-completes words it has seen in recent output, so item and mob names you can currently see are completable out of the box.
Placeholder — programmatic suggestions. tt++ has no equivalent of Mudlet’s
addCmdLineSuggestion, so there’s no clean way to feed it a live keyword list fromChar.ItemsGMCP for completion of items you haven’t seen printed. If tt++ adds a suggestion API, this section will get a real recipe.
See the GMCP Reference for the full data surface, Player Reference for the non-scripted basics, and Mudlet Samples if you also use Mudlet.
TinTin++ references: the command manual, the GMCP protocol notes, and the source (scandum/tintin).