mudgangster

Log | Files | Refs

commit 9b1dfb15505796d9b041b9c15bdd4d9b27b57955
parent 90ba5e85aa2d02c4bdd20307546d61c2fbf61b04
Author: Michael Savage <mikejsavage@gmail.com>
Date:   Mon,  3 Sep 2018 19:00:02 +0300

Pack Lua scripts into binary

Diffstat:
Makefile | 10+++++++---
action.lua | 89-------------------------------------------------------------------------------
alias.lua | 107-------------------------------------------------------------------------------
chat.lua | 228-------------------------------------------------------------------------------
connect.lua | 113-------------------------------------------------------------------------------
event.lua | 39---------------------------------------
gag.lua | 59-----------------------------------------------------------
handlers.lua | 297-------------------------------------------------------------------------------
intercept.lua | 43-------------------------------------------
interval.lua | 79-------------------------------------------------------------------------------
macro.lua | 31-------------------------------
main.lua | 69---------------------------------------------------------------------
script.lua | 143-------------------------------------------------------------------------------
scripts/merge.lua | 46++++++++++++++++++++++++++++++++++++++++++++++
scripts/pack_lua.sh | 6++++++
serialize.lua | 56--------------------------------------------------------
src/lua/action.lua | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/lua/alias.lua | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/lua/chat.lua | 228+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/lua/connect.lua | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/lua/event.lua | 39+++++++++++++++++++++++++++++++++++++++
src/lua/gag.lua | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/lua/handlers.lua | 299+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/lua/intercept.lua | 43+++++++++++++++++++++++++++++++++++++++++++
src/lua/interval.lua | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/lua/macro.lua | 31+++++++++++++++++++++++++++++++
src/lua/main.lua | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/lua/script.lua | 143+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/lua/serialize.lua | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/lua/status.lua | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/lua/sub.lua | 44++++++++++++++++++++++++++++++++++++++++++++
src/lua/utils.lua | 224+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/script.cc | 6+++++-
status.lua | 67-------------------------------------------------------------------
sub.lua | 44--------------------------------------------
utils.lua | 224-------------------------------------------------------------------------------
36 files changed, 1754 insertions(+), 1692 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,15 +1,19 @@ all: debug .PHONY: debug asan release clean -debug: +build/lua_bytecode.h: src/lua/action.lua src/lua/alias.lua src/lua/chat.lua src/lua/connect.lua src/lua/event.lua src/lua/gag.lua src/lua/handlers.lua src/lua/intercept.lua src/lua/interval.lua src/lua/macro.lua src/lua/main.lua src/lua/script.lua src/lua/serialize.lua src/lua/status.lua src/lua/sub.lua src/lua/utils.lua + @printf "\033[1;33mbuilding $@\033[0m\n" + @scripts/pack_lua.sh + +debug: build/lua_bytecode.h @lua make.lua > gen.mk @$(MAKE) -f gen.mk -asan: +asan: build/lua_bytecode.h @lua make.lua asan > gen.mk @$(MAKE) -f gen.mk -release: +release: build/lua_bytecode.h @lua make.lua release > gen.mk @$(MAKE) -f gen.mk diff --git a/action.lua b/action.lua @@ -1,89 +0,0 @@ -local Actions = { } -local PreActions = { } -local AnsiActions = { } -local AnsiPreActions = { } - -local ChatActions = { } -local ChatPreActions = { } -local ChatAnsiActions = { } -local ChatAnsiPreActions = { } - -local doActions -local doPreActions -local doAnsiActions -local doAnsiPreActions - -local doChatActions -local doChatPreActions -local doChatAnsiActions -local doChatAnsiPreActions - -local function genericActions( actions ) - return - function( pattern, callback, disabled ) - enforce( pattern, "pattern", "string" ) - enforce( callback, "callback", "function", "string" ) - - if type( callback ) == "string" then - local command = callback - - callback = function() - mud.input( command, true ) - end - end - - local action = { - pattern = pattern, - callback = callback, - - enabled = not disabled, - - enable = function( self ) - self.enabled = true - end, - disable = function( self ) - self.enabled = false - end, - } - - table.insert( actions, action ) - - return action - end, - - function( line ) - for i = 1, #actions do - local action = actions[ i ] - - if action.enabled then - local ok, err = pcall( string.gsub, line, action.pattern, action.callback ) - - if not ok then - mud.print( debug.traceback( "\n#s> action callback failed: %s" % err ) ) - end - end - end - end -end - -mud.action, doActions = genericActions( Actions ) -mud.preAction, doPreActions = genericActions( PreActions ) -mud.ansiAction, doAnsiActions = genericActions( AnsiActions ) -mud.ansiPreAction, doAnsiPreActions = genericActions( AnsiPreActions ) - -mud.chatAction, doChatActions = genericActions( ChatActions ) -mud.preChatAction, doChatPreActions = genericActions( ChatPreActions ) -mud.ansiChatAction, doChatAnsiActions = genericActions( ChatAnsiActions ) -mud.ansiPreChatAction, doChatAnsiPreActions = genericActions( ChatAnsiPreActions ) - -return { - doActions = doActions, - doPreActions = doPreActions, - doAnsiActions = doAnsiActions, - doAnsiPreActions = doAnsiPreActions, - - doChatActions = doChatActions, - doChatPreActions = doChatPreActions, - doChatAnsiActions = doChatAnsiActions, - doChatAnsiPreActions = doChatAnsiPreActions, -} diff --git a/alias.lua b/alias.lua @@ -1,107 +0,0 @@ -local Aliases = { } - -local function doAlias( line ) - local command, args = line:match( "^%s*(%S+)%s*(.*)$" ) - local alias = Aliases[ command ] - - if alias and alias.enabled then - local badSyntax = true - - for i = 1, #alias.callbacks do - local callback = alias.callbacks[ i ] - local ok, err, subs = pcall( string.gsub, args, callback.pattern, callback.callback ) - - if not ok then - mud.print( debug.traceback( "\n#s> alias callback failed: %s" % err ) ) - - return true - end - - if subs ~= 0 then - badSyntax = false - - break - end - end - - if badSyntax then - mud.print( "\nsyntax: %s %s" % { command, alias.syntax } ) - end - - return true - end - - return false -end - -local function simpleAlias( callback, disabled ) - return { - callbacks = { - { - pattern = "^(.*)$", - callback = callback, - }, - }, - - enabled = not disabled, - - enable = function( self ) - self.enabled = true - end, - disable = function( self ) - self.enabled = false - end, - } -end - -local function patternAlias( callbacks, syntax, disabled ) - local alias = { - callbacks = { }, - syntax = syntax, - - enabled = not disabled, - - enable = function( self ) - self.enabled = true - end, - disable = function( self ) - self.enabled = false - end, - } - - for pattern, callback in pairs( callbacks ) do - table.insert( alias.callbacks, { - pattern = pattern, - callback = callback, - } ) - end - - return alias -end - -function mud.alias( command, handler, ... ) - enforce( command, "command", "string" ) - enforce( handler, "handler", "function", "string", "table" ) - - assert( not Aliases[ command ], "alias `%s' already registered" % command ) - - if type( handler ) == "string" then - local command = handler - - handler = function( args ) - mud.input( command % args ) - end - end - - local alias = type( handler ) == "function" - and simpleAlias( handler, ... ) - or patternAlias( handler, ... ) - - Aliases[ command ] = alias - - return alias -end - -return { - doAlias = doAlias, -} diff --git a/chat.lua b/chat.lua @@ -1,228 +0,0 @@ -local loop = ev.Loop.default - -local handleChat - -local CommandBytes = { - all = "\4", - pm = "\5", - message = "\7", - version = "\19", -} - -local Clients = { } - -local function clientFromName( name ) - local idx = tonumber( name ) - - if idx then - return Clients[ idx ] - end - - for _, client in ipairs( Clients ) do - if client.name:startsWith( name ) then - return client - end - end - - return nil -end - -local function dataCoro( client ) - local data = coroutine.yield() - local name = data:match( "^YES:(.+)\n$" ) - - if not name then - client.socket:shutdown() - - return - end - - client.name = name - client.state = "connected" - - mud.print( "\n#s> Connected to %s@%s:%s", client.name, client.address, client.port ) - - local dataBuffer = "" - - while true do - dataBuffer = dataBuffer .. coroutine.yield() - - dataBuffer = dataBuffer:gsub( "(.)(.-)\255", function( command, args ) - if command == CommandBytes.all or command == CommandBytes.pm or command == CommandBytes.message then - local message = args:match( "^\n*(.-)\n*$" ) - - handleChat( message:gsub( "\r", "" ) ) - end - - return "" - end ) - end -end - -local Client = { } - -function Client:new( socket, address, port ) - socket:setoption( "keepalive", true ) - - local client = { - socket = socket, - - address = address, - port = port, - - state = "connecting", - handler = coroutine.create( dataCoro ), - } - - assert( coroutine.resume( client.handler, client ) ) - - setmetatable( client, { __index = Client } ) - - table.insert( Clients, client ) - - socket:send( "CHAT:Hirve\n127.0.0.14050 " ) - - return client -end - -function Client:kill() - if self.state == "killed" then - return - end - - mud.print( "\n#s> Disconnected from %s!", self.name ) - - self.state = "killed" - self.socket:shutdown() - - for i, client in ipairs( Clients ) do - if client == self then - table.remove( Clients, i ) - break - end - end -end - -local function dataHandler( client, loop, watcher ) - local _, err, data = client.socket:receive( "*a" ) - - if err == "closed" then - client:kill() - watcher:stop( loop ) - - return - end - - assert( coroutine.resume( client.handler, data ) ) - mud.handleXEvents() -end - -function mud.chatns( form, ... ) - local named = "\nHirve" .. form:format( ... ) - local data = CommandBytes.all .. named:parseColours() .. "\n\255" - - for _, client in ipairs( Clients ) do - if client.state == "connected" then - client.socket:send( data ) - end - end - - mud.printb( "#lr%s", named ) - handleChat() -end - -function mud.chat( form, ... ) - mud.chatns( " " .. form, ... ) -end - -local function call( address, port ) - mud.print( "\n#s> Calling %s:%d...", address, port ) - - local sock = socket.tcp() - sock:settimeout( 0 ) - - sock:connect( address, port ) - - ev.IO.new( function( loop, watcher ) - local _, err = sock:receive( "*a" ) - - if err ~= "connection refused" then - local client = Client:new( sock, address, port ) - - ev.IO.new( function( loop, watcher ) - dataHandler( client, loop, watcher ) - end, sock:getfd(), ev.READ ):start( loop ) - - ev.IO.new( function( loop, watcher ) - client.socket:send( CommandBytes.version .. "MudGangster" .. "\255" ) - watcher:stop( loop ) - end, sock:getfd(), ev.WRITE ):start( loop ) - else - mud.print( "\n#s> Failed to call %s:%d", address, port ) - end - - watcher:stop( loop ) - end, sock:getfd(), ev.WRITE ):start( loop ) -end - -mud.alias( "/call", { - [ "^(%S+)$" ] = function( address ) - call( address, 4050 ) - end, - - [ "^(%S+)[%s:]+(%d+)$" ] = call, -}, "<address> [port]" ) - -mud.alias( "/hang", { - [ "^(%S+)$" ] = function( name ) - local client = clientFromName( name ) - - if client then - client:kill() - end - end, -} ) - -local function sendPM( client, message ) - local named = "\nHirve chats to you, '" .. message .. "'" - local data = CommandBytes.pm .. named .. "\n\255" - - client.socket:send( data ) -end - -mud.alias( "/silentpm", { - [ "^(%S+)%s+(.-)$" ] = function( name, message ) - local client = clientFromName( name ) - - if client then - local coloured = message:parseColours() - - sendPM( client, coloured ) - end - end, -} ) - -mud.alias( "/pm", { - [ "^(%S+)%s+(.-)$" ] = function( name, message ) - local client = clientFromName( name ) - - if client then - local coloured = message:parseColours() - - sendPM( client, coloured ) - - local toPrint = ( "You chat to %s, '" % client.name ) .. coloured .. "'" - - handleChat( toPrint ) - end - end, -} ) - -mud.alias( "/emoteto", function( args ) -end ) - -return { - init = function( chatHandler ) - handleChat = chatHandler - end, -} diff --git a/connect.lua b/connect.lua @@ -1,113 +0,0 @@ -local DataHandler - -local LastAddress -local LastPort - -local loop = ev.Loop.default - -function mud.disconnect() - if not mud.connected then - mud.print( "\n#s> You're not connected..." ) - - return - end - - mud.connected = false - - mud.kill() -end - -function mud.connect( address, port ) - if mud.connected then - mud.print( "\n#s> Already connected! (%s:%d)", LastAddress, LastPort ) - - return - end - - if not address then - if not LastAddress then - mud.print( "\n#s> I need an address..." ) - - return - end - - address = LastAddress - port = LastPort - end - - local sock = socket.tcp() - sock:settimeout( 0 ) - sock:connect( address, port ) - - mud.print( "\n#s> Connecting to %s:%d...", address, port ) - - mud.connected = true - mud.lastInput = mud.now() - - mud.send = function( data ) - mud.lastInput = mud.now() - - sock:send( data ) - end - - mud.kill = function() - sock:shutdown() - end - - ev.IO.new( function( loop, watcher ) - local _, err = sock:receive( "*a" ) - - if err ~= "connection refused" then - LastAddress = address - LastPort = port - - ev.IO.new( function( loop, watcher ) - local _, err, data = sock:receive( "*a" ) - - if not data then - data = _ - end - - if data then - DataHandler( data ) - mud.handleXEvents() - end - - if err == "closed" then - mud.print( "\n#s> Disconnected!" ) - - watcher:stop( loop ) - sock:shutdown() - - mud.connected = false - - return - end - end, sock:getfd(), ev.READ ):start( loop ) - else - mud.print( "\n#s> Refused!" ) - - mud.connected = false - end - - watcher:stop( loop ) - end, sock:getfd(), ev.WRITE ):start( loop ) -end - -mud.alias( "/con", { - [ "^$" ] = function() - mud.connect() - end, - - [ "^(%S+)%s+(%d+)$" ] = function( address, port ) - mud.connect( address, port ) - end, -}, "<ip> <port>" ) - -mud.alias( "/dc", mud.disconnect ) - -return { - init = function( dataHandler ) - DataHandler = dataHandler - end, -} diff --git a/event.lua b/event.lua @@ -1,39 +0,0 @@ -local Events = { } - -function mud.listen( name, callback, disabled ) - enforce( name, "name", "string" ) - enforce( callback, "callback", "function" ) - - local event = { - callback = callback, - - enabled = not disabled, - - enable = function( self ) - self.enabled = true - end, - disable = function( self ) - self.enabled = false - end, - } - - if not Events[ name ] then - Events[ name ] = { } - end - - table.insert( Events[ name ], event ) - - return event -end - -function mud.event( name, ... ) - enforce( name, "name", "string" ) - - if Events[ name ] then - for _, event in ipairs( Events[ name ] ) do - if event.enabled then - event.callback( ... ) - end - end - end -end diff --git a/gag.lua b/gag.lua @@ -1,59 +0,0 @@ -local Gags = { } -local AnsiGags = { } - -local ChatGags = { } -local AnsiChatGags = { } - -local doGags -local doAnsiGags - -local doChatGags -local doAnsiChatGags - -local function genericGags( gags ) - return - function( pattern, disabled ) - local gag = { - pattern = pattern, - - enabled = not disabled, - - enable = function( self ) - self.enabled = true - end, - disable = function( self ) - self.enabled = false - end, - } - - table.insert( gags, gag ) - - return gag - end, - - function( line ) - for i = 1, #gags do - local gag = gags[ i ] - - if gag.enabled and line:find( gag.pattern ) then - return true - end - end - - return false - end -end - -mud.gag, doGags = genericGags( Gags ) -mud.gagAnsi, doAnsiGags = genericGags( AnsiGags ) - -mud.gagChat, doChatGags = genericGags( ChatGags ) -mud.gagAnsiChat, doChatAnsiGags = genericGags( ChatAnsiGags ) - -return { - doGags = doGags, - doAnsiGags = doAnsiGags, - - doChatGags = doChatGags, - doChatAnsiGags = doChatAnsiGags, -} diff --git a/handlers.lua b/handlers.lua @@ -1,297 +0,0 @@ -local action = require( "action" ) -local alias = require( "alias" ) -local intercept = require( "intercept" ) -local gag = require( "gag" ) -local macro = require( "macro" ) -local sub = require( "sub" ) -local interval = require( "interval" ) - -local GA = "\255\249" - -local bold = false -local fg = 7 -local bg = 0 - -local lastWasChat = false -local lastWasGA = false - -local receiving = false -local showInput = true - -local dataBuffer = "" -local pendingInputs = { } - -local function echoOn() - showInput = true - - return "" -end - -local function echoOff() - showInput = true - - return "" -end - -local function setFG( colour ) - return function() - fg = colour - end -end - -local function setBG( colour ) - return function() - bg = colour - end -end - -local Escapes = { - m = { - [ "0" ] = function() - bold = false - fg = 7 - bg = 0 - end, - - [ "1" ] = function() - bold = true - end, - - [ "30" ] = setFG( 0 ), - [ "31" ] = setFG( 1 ), - [ "32" ] = setFG( 2 ), - [ "33" ] = setFG( 3 ), - [ "34" ] = setFG( 4 ), - [ "35" ] = setFG( 5 ), - [ "36" ] = setFG( 6 ), - [ "37" ] = setFG( 7 ), - - [ "40" ] = setBG( 0 ), - [ "41" ] = setBG( 1 ), - [ "42" ] = setBG( 2 ), - [ "43" ] = setBG( 3 ), - [ "44" ] = setBG( 4 ), - [ "45" ] = setBG( 5 ), - [ "46" ] = setBG( 6 ), - [ "47" ] = setBG( 7 ), - }, -} - -local function printPendingInputs() - if lastWasChat then - mud.newlineMain() - - lastWasChat = false - end - - for i = 1, #pendingInputs do - mud.printr( pendingInputs[ i ] ) - - lastWasGA = false - end - - pendingInputs = { } -end - -local function handleChat( message ) - lastWasChat = true - lastWasGA = false - - if not message then - return - end - - local oldFG = fg - local oldBG = bg - local oldBold = bold - - fg = 1 - bg = 0 - bold = true - - local noAnsi = message:gsub( "\27%[[%d;]*%a", "" ) - - action.doChatPreActions( noAnsi ) - action.doChatAnsiPreActions( message ) - - mud.newlineMain() - mud.newlineChat() - - for line, newLine in message:gmatch( "([^\n]*)(\n?)" ) do - for text, opts, escape in ( line .. "\27[m" ):gmatch( "(.-)\27%[([%d;]*)(%a)" ) do - if text ~= "" then - mud.printMain( text, fg, bg, bold ) - mud.printChat( text, fg, bg, bold ) - end - - for opt in opts:gmatch( "([^;]+)" ) do - if Escapes[ escape ][ opt ] then - Escapes[ escape ][ opt ]() - end - end - end - - if newLine == "\n" then - mud.newlineMain() - mud.newlineChat() - end - end - - mud.drawMain() - mud.drawChat() - - action.doChatActions( noAnsi ) - action.doChatAnsiActions( message ) - - fg = oldFG - bg = oldBG - bold = oldBold -end - -local function handleData( data ) - receiving = true - - dataBuffer = dataBuffer .. data - - if data:match( GA ) then - dataBuffer = dataBuffer:gsub( "\r", "" ) - - dataBuffer = dataBuffer:gsub( "\255\252\1", echoOn ) - dataBuffer = dataBuffer:gsub( "\255\251\1", echoOff ) - - dataBuffer = dataBuffer:gsub( "\255\253\24", "" ) - dataBuffer = dataBuffer:gsub( "\255\253\31", "" ) - dataBuffer = dataBuffer:gsub( "\255\253\34", "" ) - - dataBuffer = dataBuffer .. "\n" - - for line in dataBuffer:gmatch( "([^\n]*)\n" ) do - if lastWasGA then - if line ~= "" then - mud.newlineMain() - end - - lastWasGA = false - end - - if lastWasChat then - mud.newlineMain() - - lastWasChat = false - end - - local clean, subs = line:gsub( GA, "" ) - local hasGA = subs ~= 0 - - local noAnsi = clean:gsub( "\27%[%d*%a", "" ) - - local gagged = gag.doGags( noAnsi ) or gag.doAnsiGags( clean ) - - action.doPreActions( noAnsi ) - action.doAnsiPreActions( clean ) - - local subbed = sub.doSubs( clean ) - - for text, opts, escape in ( subbed .. "\27[m" ):gmatch( "(.-)\27%[([%d;]*)(%a)" ) do - if text ~= "" and not gagged then - mud.printMain( text, fg, bg, bold ) - end - - for opt in opts:gmatch( "([^;]+)" ) do - if Escapes[ escape ][ opt ] then - Escapes[ escape ][ opt ]() - end - end - end - - if hasGA then - lastWasGA = true - printPendingInputs() - else - if not gagged then - mud.newlineMain() - end - end - - action.doActions( noAnsi ) - action.doAnsiActions( clean ) - end - - mud.drawMain() - - dataBuffer = "" - receiving = false - end -end - -local function handleCommand( input, hide ) - if not alias.doAlias( input ) then - if not mud.connected then - mud.print( "\n#s> You're not connected..." ) - - return - end - - intercept.doIntercept( input ) - - if not hide and input ~= "" then - local toShow = ( showInput and input or ( "*" ):rep( input:len() ) ) .. "\n" - - if receiving then - table.insert( pendingInputs, toShow ) - else - if lastWasChat then - mud.newlineMain() - - lastWasChat = false - end - - lastWasGA = false - - mud.printr( toShow ) - end - end - - mud.send( input .. "\n" ) - mud.drawMain() - end -end - -local function handleInput( input, display ) - for command in ( input .. ";" ):gmatch( "([^;]*);" ) do - handleCommand( command, display ) - end -end - -mud.input = handleInput - -local function handleMacro( key, shift, ctrl, alt ) - if shift then - key = "s" .. key - end - - if ctrl then - key = "c" .. key - end - - if alt then - key = "a" .. key - end - - macro.doMacro( key ) -end - -local function handleClose() - ev.Loop.default:unloop() - - mud.event( "shutdown" ) -end - -return { - data = handleData, - chat = handleChat, - input = handleInput, - macro = handleMacro, - interval = interval.doIntervals, - close = handleClose, -} diff --git a/intercept.lua b/intercept.lua @@ -1,43 +0,0 @@ -local Intercepts = { } - -local function doIntercept( command ) - for i = 1, #Intercepts do - local intercept = Intercepts[ i ] - - if intercept.enabled then - local ok, err = pcall( string.gsub, command, intercept.pattern, intercept.callback ) - - if not ok then - mud.print( debug.traceback( "\n#s> intercept callback failed: %s" % err ) ) - end - end - end -end - -function mud.intercept( pattern, callback, disabled ) - enforce( pattern, "pattern", "string" ) - enforce( callback, "callback", "function" ) - enforce( disabled, "disabled", "boolean", "nil" ) - - local intercept = { - pattern = pattern, - callback = callback, - - enabled = not disabled, - - enable = function( self ) - self.enabled = true - end, - disable = function( self ) - self.enabled = false - end, - } - - table.insert( Intercepts, intercept ) - - return intercept -end - -return { - doIntercept = doIntercept, -} diff --git a/interval.lua b/interval.lua @@ -1,79 +0,0 @@ -local Intervals = { } - -local function doIntervals( loop, watcher ) - local now = loop:update_now() - - for i = 1, #Intervals do - local event = Intervals[ i ] - - if event.enabled then - event:checkTick( now ) - end - end -end - -function mud.now() - return ev.Loop.default:update_now() -end - -function mud.interval( callback, interval, disabled ) - enforce( callback, "callback", "function" ) - enforce( interval, "interval", "number" ) - - local event = { - callback = callback, - interval = interval, - nextTick = mud.now(), - - enabled = not disabled, - - enable = function( self ) - self.enabled = true - end, - disable = function( self ) - self.enabled = false - end, - - tick = function( self, now ) - callback( now ) - - self.nextTick = now + self.interval - end, - - checkTick = function( self, now ) - if self.nextTick == -1 then - self.nextTick = now - end - - if now >= self.nextTick then - self:tick( now ) - end - end, - - tend = function( self, tolerance, desired ) - tolerance = tolerance or 0 - desired = desired or mud.now() - - local toTick = ( self.nextTick - desired ) % self.interval - local sinceTick = self.interval - toTick - - if toTick <= tolerance then - self.nextTick = math.avg( self.nextTick, desired ) - elseif sinceTick <= tolerance then - self.nextTick = math.avg( self.nextTick, desired + self.interval ) - else - self.nextTick = desired - - self:checkTick( mud.now() ) - end - end, - } - - table.insert( Intervals, event ) - - return event -end - -return { - doIntervals = doIntervals, -} diff --git a/macro.lua b/macro.lua @@ -1,31 +0,0 @@ -local Macros = { } - -local function doMacro( key ) - local macro = Macros[ key ] - - if macro then - macro() - end -end - -function mud.macro( key, callback, disabled ) - enforce( key, "key", "string" ) - enforce( callback, "callback", "function", "string" ) - - if type( callback ) == "string" then - local command = callback - - callback = function() - mud.input( command ) - end - end - - assert( not Macros[ key ], "macro `%s' already registered" % key ) - - - Macros[ key ] = callback -end - -return { - doMacro = doMacro, -} diff --git a/main.lua b/main.lua @@ -1,69 +0,0 @@ -mud = { - connected = false, -} - --- local function luarocks_path( opt, orig ) --- local pipe = io.popen( "luarocks path " .. opt, "r" ) --- if not pipe then --- return nil --- end --- --- local contents = pipe:read( "*all" ) --- pipe:close() --- --- if not contents then --- return orig --- end --- --- return contents:gsub( "%s*$", "" ) .. ";" .. orig --- end --- --- package.path = luarocks_path( "--lr-path", package.path ) --- package.cpath = luarocks_path( "--lr-cpath", package.cpath ) - -table.unpack = table.unpack or unpack - -require( "utils" ) - -socket = require( "socket" ) -ev = require( "ev" ) - -require( "event" ) - -local script = require( "script" ) -local handlers = require( "handlers" ) - -require( "chat" ).init( handlers.chat ) -require( "connect" ).init( handlers.data ) - -local xFD, handleXEvents, - printMain, newlineMain, drawMain, - printChat, newlineChat, drawChat, - setHandlers, urgent, setStatus = ... - -mud.printMain = printMain -mud.newlineMain = newlineMain -mud.drawMain = drawMain - -mud.printChat = printChat -mud.newlineChat = newlineChat -mud.drawChat = drawChat - -mud.urgent = urgent - -require( "status" ).init( setStatus ) - -setHandlers( handlers.input, handlers.macro, handlers.close ) - -local loop = ev.Loop.default - -mud.handleXEvents = handleXEvents - -ev.IO.new( handleXEvents, xFD, ev.READ ):start( loop ) -ev.Timer.new( handlers.interval, 0.5, 0.5 ):start( loop ) - -script.load() - -loop:loop() - -script.close() diff --git a/script.lua b/script.lua @@ -1,143 +0,0 @@ -local lfs = require( "lfs" ) -local serialize = require( "serialize" ) - -local ScriptsDir = os.getenv( "HOME" ) .. "/.mudgangster/scripts" - -package.path = package.path .. ";" .. ScriptsDir .. "/?.lua" - -local function loadScript( name, path, padding ) - local function throw( err ) - if err:match( "^" .. path ) then - err = err:sub( path:len() + 2 ) - end - - mud.print( "#lr%-" .. padding .. "s", name ) - mud.print( "#s\n>\n> %s\n>", err ) - end - - mud.print( "#s\n> " ) - - local mainPath = path .. "/main.lua" - local readable, err = io.readable( mainPath ) - - if not readable then - throw( err ) - - return - end - - local env = setmetatable( - { - require = function( requirePath, ... ) - return require( "%s.%s" % { name, requirePath }, ... ) - end, - - saved = function( defaults ) - local settingsPath = path .. "/settings.lua" - - mud.listen( "shutdown", function() - local file = io.open( settingsPath, "w" ) - - file:write( serialize.unwrapped( defaults ) ) - - file:close() - end ) - - local settingsEnv = setmetatable( { }, { - __newindex = defaults, - } ) - - local settings = loadfile( settingsPath, "t", settingsEnv ) - - if not settings then - return defaults - end - - if _VERSION == "Lua 5.1" then - setfenv( settings, settingsEnv ) - end - - pcall( settings ) - - return defaults - end, - }, - { - __index = _G, - __newindex = _G, - } - ) - - local script, err = loadfile( mainPath, "t", env ) - - if not script then - throw( err ) - - return - end - - if _VERSION == "Lua 5.1" then - setfenv( script, env ) - end - - local loaded, err = pcall( script ) - - if loaded then - mud.print( "#lg%s", name ) - else - throw( err ) - end -end - -local function loadScripts() - mud.print( "#s> Loading scripts..." ) - - local readable, err = io.readable( ScriptsDir ) - - if readable then - local attr = lfs.attributes( ScriptsDir ) - - if attr.mode == "directory" then - local scripts = { } - local maxLen = 0 - - for script in lfs.dir( ScriptsDir ) do - if not script:match( "^%." ) then - local path = ScriptsDir .. "/" .. script - local scriptAttr = lfs.attributes( path ) - - if scriptAttr.mode == "directory" then - maxLen = math.max( script:len(), maxLen ) - - table.insert( scripts, { - name = script, - path = path, - } ) - end - end - end - - table.sort( scripts, function( a, b ) - return a.name < b.name - end ) - - for _, script in ipairs( scripts ) do - loadScript( script.name, script.path, maxLen ) - end - else - mud.print( "#sfailed!\n> `%s' isn't a directory", ScriptsDir ) - end - else - mud.print( "#sfailed!\n> %s", err ) - end - - mud.event( "scriptsLoaded" ) -end - -local function closeScripts() -end - -return { - load = loadScripts, - close = closeScripts, -} diff --git a/scripts/merge.lua b/scripts/merge.lua @@ -0,0 +1,46 @@ +if not arg[ 1 ] or not arg[ 2 ] then + print( arg[ 0 ] .. " <source directory> <path to main> [Lua version]" ) + os.exit( 1 ) +end + +local lfs = require( "lfs" ) + +local merged = { "#! /usr/bin/env lua" .. ( arg[ 3 ] or "" ) } + +local root = arg[ 1 ] +local main = arg[ 2 ] + +local function addDir( rel ) + for file in lfs.dir( root .. "/" .. rel ) do + if file ~= "." and file ~= ".." then + local full = root .. "/" .. rel .. file + local attr = lfs.attributes( full ) + + if attr.mode == "directory" then + addDir( rel .. file .. "/" ) + elseif file:match( "%.lua$" ) and ( rel ~= "" or file ~= main ) then + local f = io.open( full, "r" ) + local contents = f:read( "*all" ) + f:close() + + table.insert( merged, ( [[ + package.preload[ "%s" ] = function( ... ) + %s + end]] ):format( + ( rel .. file ):gsub( "%.lua$", "" ):gsub( "/", "." ), + contents + ) + ) + end + end + end +end + +addDir( "" ) + +local f = io.open( root .. "/" .. main, "r" ) +local contents = f:read( "*all" ) +f:close() + +print( table.concat( merged, "\n" ) ) +print( contents ) diff --git a/scripts/pack_lua.sh b/scripts/pack_lua.sh @@ -0,0 +1,6 @@ +#! /bin/sh + +set -o pipefail + +mkdir -p build +exec lua scripts/merge.lua src/lua main.lua | luac -o - - | xxd -i > build/lua_bytecode.h diff --git a/serialize.lua b/serialize.lua @@ -1,56 +0,0 @@ --- can't handle cycles, only works on strings/numbers/bools/tables - -local function formatKey( key ) - if type( key ) == "string" then - return "[ %q ]" % key - end - - return "[ %s ]" % tostring( key ) -end - -local function serializeObject( obj ) - local t = type( obj ) - - if t == "number" or t == "boolean" then - return tostring( obj ) - end - - if t == "string" then - return "%q" % obj - end - - if t == "table" then - local output = "{ " - - for k, v in pairs( obj ) do - output = output .. "%s = %s, " % { formatKey( k ), serializeObject( v ) } - end - - return output .. "}" - end - - error( "I don't know how to serialize type " .. t ) -end - -local function serialize( obj ) - if not obj then - return "return { }" - end - - return "return " .. serializeObject( obj ) -end - -local function serializeUnwrapped( obj ) - local output = "" - - for k, v in pairs( obj ) do - output = output .. "%s = %s\n" % { k, serializeObject( v ) } - end - - return output -end - -return { - wrapped = serialize, - unwrapped = serializeUnwrapped, -} diff --git a/src/lua/action.lua b/src/lua/action.lua @@ -0,0 +1,89 @@ +local Actions = { } +local PreActions = { } +local AnsiActions = { } +local AnsiPreActions = { } + +local ChatActions = { } +local ChatPreActions = { } +local ChatAnsiActions = { } +local ChatAnsiPreActions = { } + +local doActions +local doPreActions +local doAnsiActions +local doAnsiPreActions + +local doChatActions +local doChatPreActions +local doChatAnsiActions +local doChatAnsiPreActions + +local function genericActions( actions ) + return + function( pattern, callback, disabled ) + enforce( pattern, "pattern", "string" ) + enforce( callback, "callback", "function", "string" ) + + if type( callback ) == "string" then + local command = callback + + callback = function() + mud.input( command, true ) + end + end + + local action = { + pattern = pattern, + callback = callback, + + enabled = not disabled, + + enable = function( self ) + self.enabled = true + end, + disable = function( self ) + self.enabled = false + end, + } + + table.insert( actions, action ) + + return action + end, + + function( line ) + for i = 1, #actions do + local action = actions[ i ] + + if action.enabled then + local ok, err = pcall( string.gsub, line, action.pattern, action.callback ) + + if not ok then + mud.print( debug.traceback( "\n#s> action callback failed: %s" % err ) ) + end + end + end + end +end + +mud.action, doActions = genericActions( Actions ) +mud.preAction, doPreActions = genericActions( PreActions ) +mud.ansiAction, doAnsiActions = genericActions( AnsiActions ) +mud.ansiPreAction, doAnsiPreActions = genericActions( AnsiPreActions ) + +mud.chatAction, doChatActions = genericActions( ChatActions ) +mud.preChatAction, doChatPreActions = genericActions( ChatPreActions ) +mud.ansiChatAction, doChatAnsiActions = genericActions( ChatAnsiActions ) +mud.ansiPreChatAction, doChatAnsiPreActions = genericActions( ChatAnsiPreActions ) + +return { + doActions = doActions, + doPreActions = doPreActions, + doAnsiActions = doAnsiActions, + doAnsiPreActions = doAnsiPreActions, + + doChatActions = doChatActions, + doChatPreActions = doChatPreActions, + doChatAnsiActions = doChatAnsiActions, + doChatAnsiPreActions = doChatAnsiPreActions, +} diff --git a/src/lua/alias.lua b/src/lua/alias.lua @@ -0,0 +1,107 @@ +local Aliases = { } + +local function doAlias( line ) + local command, args = line:match( "^%s*(%S+)%s*(.*)$" ) + local alias = Aliases[ command ] + + if alias and alias.enabled then + local badSyntax = true + + for i = 1, #alias.callbacks do + local callback = alias.callbacks[ i ] + local ok, err, subs = pcall( string.gsub, args, callback.pattern, callback.callback ) + + if not ok then + mud.print( debug.traceback( "\n#s> alias callback failed: %s" % err ) ) + + return true + end + + if subs ~= 0 then + badSyntax = false + + break + end + end + + if badSyntax then + mud.print( "\nsyntax: %s %s" % { command, alias.syntax } ) + end + + return true + end + + return false +end + +local function simpleAlias( callback, disabled ) + return { + callbacks = { + { + pattern = "^(.*)$", + callback = callback, + }, + }, + + enabled = not disabled, + + enable = function( self ) + self.enabled = true + end, + disable = function( self ) + self.enabled = false + end, + } +end + +local function patternAlias( callbacks, syntax, disabled ) + local alias = { + callbacks = { }, + syntax = syntax, + + enabled = not disabled, + + enable = function( self ) + self.enabled = true + end, + disable = function( self ) + self.enabled = false + end, + } + + for pattern, callback in pairs( callbacks ) do + table.insert( alias.callbacks, { + pattern = pattern, + callback = callback, + } ) + end + + return alias +end + +function mud.alias( command, handler, ... ) + enforce( command, "command", "string" ) + enforce( handler, "handler", "function", "string", "table" ) + + assert( not Aliases[ command ], "alias `%s' already registered" % command ) + + if type( handler ) == "string" then + local command = handler + + handler = function( args ) + mud.input( command % args ) + end + end + + local alias = type( handler ) == "function" + and simpleAlias( handler, ... ) + or patternAlias( handler, ... ) + + Aliases[ command ] = alias + + return alias +end + +return { + doAlias = doAlias, +} diff --git a/src/lua/chat.lua b/src/lua/chat.lua @@ -0,0 +1,228 @@ +local loop = ev.Loop.default + +local handleChat + +local CommandBytes = { + all = "\4", + pm = "\5", + message = "\7", + version = "\19", +} + +local Clients = { } + +local function clientFromName( name ) + local idx = tonumber( name ) + + if idx then + return Clients[ idx ] + end + + for _, client in ipairs( Clients ) do + if client.name:startsWith( name ) then + return client + end + end + + return nil +end + +local function dataCoro( client ) + local data = coroutine.yield() + local name = data:match( "^YES:(.+)\n$" ) + + if not name then + client.socket:shutdown() + + return + end + + client.name = name + client.state = "connected" + + mud.print( "\n#s> Connected to %s@%s:%s", client.name, client.address, client.port ) + + local dataBuffer = "" + + while true do + dataBuffer = dataBuffer .. coroutine.yield() + + dataBuffer = dataBuffer:gsub( "(.)(.-)\255", function( command, args ) + if command == CommandBytes.all or command == CommandBytes.pm or command == CommandBytes.message then + local message = args:match( "^\n*(.-)\n*$" ) + + handleChat( message:gsub( "\r", "" ) ) + end + + return "" + end ) + end +end + +local Client = { } + +function Client:new( socket, address, port ) + socket:setoption( "keepalive", true ) + + local client = { + socket = socket, + + address = address, + port = port, + + state = "connecting", + handler = coroutine.create( dataCoro ), + } + + assert( coroutine.resume( client.handler, client ) ) + + setmetatable( client, { __index = Client } ) + + table.insert( Clients, client ) + + socket:send( "CHAT:Hirve\n127.0.0.14050 " ) + + return client +end + +function Client:kill() + if self.state == "killed" then + return + end + + mud.print( "\n#s> Disconnected from %s!", self.name ) + + self.state = "killed" + self.socket:shutdown() + + for i, client in ipairs( Clients ) do + if client == self then + table.remove( Clients, i ) + break + end + end +end + +local function dataHandler( client, loop, watcher ) + local _, err, data = client.socket:receive( "*a" ) + + if err == "closed" then + client:kill() + watcher:stop( loop ) + + return + end + + assert( coroutine.resume( client.handler, data ) ) + mud.handleXEvents() +end + +function mud.chatns( form, ... ) + local named = "\nHirve" .. form:format( ... ) + local data = CommandBytes.all .. named:parseColours() .. "\n\255" + + for _, client in ipairs( Clients ) do + if client.state == "connected" then + client.socket:send( data ) + end + end + + mud.printb( "#lr%s", named ) + handleChat() +end + +function mud.chat( form, ... ) + mud.chatns( " " .. form, ... ) +end + +local function call( address, port ) + mud.print( "\n#s> Calling %s:%d...", address, port ) + + local sock = socket.tcp() + sock:settimeout( 0 ) + + sock:connect( address, port ) + + ev.IO.new( function( loop, watcher ) + local _, err = sock:receive( "*a" ) + + if err ~= "connection refused" then + local client = Client:new( sock, address, port ) + + ev.IO.new( function( loop, watcher ) + dataHandler( client, loop, watcher ) + end, sock:getfd(), ev.READ ):start( loop ) + + ev.IO.new( function( loop, watcher ) + client.socket:send( CommandBytes.version .. "MudGangster" .. "\255" ) + watcher:stop( loop ) + end, sock:getfd(), ev.WRITE ):start( loop ) + else + mud.print( "\n#s> Failed to call %s:%d", address, port ) + end + + watcher:stop( loop ) + end, sock:getfd(), ev.WRITE ):start( loop ) +end + +mud.alias( "/call", { + [ "^(%S+)$" ] = function( address ) + call( address, 4050 ) + end, + + [ "^(%S+)[%s:]+(%d+)$" ] = call, +}, "<address> [port]" ) + +mud.alias( "/hang", { + [ "^(%S+)$" ] = function( name ) + local client = clientFromName( name ) + + if client then + client:kill() + end + end, +} ) + +local function sendPM( client, message ) + local named = "\nHirve chats to you, '" .. message .. "'" + local data = CommandBytes.pm .. named .. "\n\255" + + client.socket:send( data ) +end + +mud.alias( "/silentpm", { + [ "^(%S+)%s+(.-)$" ] = function( name, message ) + local client = clientFromName( name ) + + if client then + local coloured = message:parseColours() + + sendPM( client, coloured ) + end + end, +} ) + +mud.alias( "/pm", { + [ "^(%S+)%s+(.-)$" ] = function( name, message ) + local client = clientFromName( name ) + + if client then + local coloured = message:parseColours() + + sendPM( client, coloured ) + + local toPrint = ( "You chat to %s, '" % client.name ) .. coloured .. "'" + + handleChat( toPrint ) + end + end, +} ) + +mud.alias( "/emoteto", function( args ) +end ) + +return { + init = function( chatHandler ) + handleChat = chatHandler + end, +} diff --git a/src/lua/connect.lua b/src/lua/connect.lua @@ -0,0 +1,113 @@ +local DataHandler + +local LastAddress +local LastPort + +local loop = ev.Loop.default + +function mud.disconnect() + if not mud.connected then + mud.print( "\n#s> You're not connected..." ) + + return + end + + mud.connected = false + + mud.kill() +end + +function mud.connect( address, port ) + if mud.connected then + mud.print( "\n#s> Already connected! (%s:%d)", LastAddress, LastPort ) + + return + end + + if not address then + if not LastAddress then + mud.print( "\n#s> I need an address..." ) + + return + end + + address = LastAddress + port = LastPort + end + + local sock = socket.tcp() + sock:settimeout( 0 ) + sock:connect( address, port ) + + mud.print( "\n#s> Connecting to %s:%d...", address, port ) + + mud.connected = true + mud.lastInput = mud.now() + + mud.send = function( data ) + mud.lastInput = mud.now() + + sock:send( data ) + end + + mud.kill = function() + sock:shutdown() + end + + ev.IO.new( function( loop, watcher ) + local _, err = sock:receive( "*a" ) + + if err ~= "connection refused" then + LastAddress = address + LastPort = port + + ev.IO.new( function( loop, watcher ) + local _, err, data = sock:receive( "*a" ) + + if not data then + data = _ + end + + if data then + DataHandler( data ) + mud.handleXEvents() + end + + if err == "closed" then + mud.print( "\n#s> Disconnected!" ) + + watcher:stop( loop ) + sock:shutdown() + + mud.connected = false + + return + end + end, sock:getfd(), ev.READ ):start( loop ) + else + mud.print( "\n#s> Refused!" ) + + mud.connected = false + end + + watcher:stop( loop ) + end, sock:getfd(), ev.WRITE ):start( loop ) +end + +mud.alias( "/con", { + [ "^$" ] = function() + mud.connect() + end, + + [ "^(%S+)%s+(%d+)$" ] = function( address, port ) + mud.connect( address, port ) + end, +}, "<ip> <port>" ) + +mud.alias( "/dc", mud.disconnect ) + +return { + init = function( dataHandler ) + DataHandler = dataHandler + end, +} diff --git a/src/lua/event.lua b/src/lua/event.lua @@ -0,0 +1,39 @@ +local Events = { } + +function mud.listen( name, callback, disabled ) + enforce( name, "name", "string" ) + enforce( callback, "callback", "function" ) + + local event = { + callback = callback, + + enabled = not disabled, + + enable = function( self ) + self.enabled = true + end, + disable = function( self ) + self.enabled = false + end, + } + + if not Events[ name ] then + Events[ name ] = { } + end + + table.insert( Events[ name ], event ) + + return event +end + +function mud.event( name, ... ) + enforce( name, "name", "string" ) + + if Events[ name ] then + for _, event in ipairs( Events[ name ] ) do + if event.enabled then + event.callback( ... ) + end + end + end +end diff --git a/src/lua/gag.lua b/src/lua/gag.lua @@ -0,0 +1,59 @@ +local Gags = { } +local AnsiGags = { } + +local ChatGags = { } +local AnsiChatGags = { } + +local doGags +local doAnsiGags + +local doChatGags +local doAnsiChatGags + +local function genericGags( gags ) + return + function( pattern, disabled ) + local gag = { + pattern = pattern, + + enabled = not disabled, + + enable = function( self ) + self.enabled = true + end, + disable = function( self ) + self.enabled = false + end, + } + + table.insert( gags, gag ) + + return gag + end, + + function( line ) + for i = 1, #gags do + local gag = gags[ i ] + + if gag.enabled and line:find( gag.pattern ) then + return true + end + end + + return false + end +end + +mud.gag, doGags = genericGags( Gags ) +mud.gagAnsi, doAnsiGags = genericGags( AnsiGags ) + +mud.gagChat, doChatGags = genericGags( ChatGags ) +mud.gagAnsiChat, doChatAnsiGags = genericGags( ChatAnsiGags ) + +return { + doGags = doGags, + doAnsiGags = doAnsiGags, + + doChatGags = doChatGags, + doChatAnsiGags = doChatAnsiGags, +} diff --git a/src/lua/handlers.lua b/src/lua/handlers.lua @@ -0,0 +1,299 @@ +local action = require( "action" ) +local alias = require( "alias" ) +local intercept = require( "intercept" ) +local gag = require( "gag" ) +local macro = require( "macro" ) +local sub = require( "sub" ) +local interval = require( "interval" ) + +local lpeg = require( "lpeg" ) + +local GA = "\255\249" + +local bold = false +local fg = 7 +local bg = 0 + +local lastWasChat = false +local lastWasGA = false + +local receiving = false +local showInput = true + +local dataBuffer = "" +local pendingInputs = { } + +local function echoOn() + showInput = true + + return "" +end + +local function echoOff() + showInput = true + + return "" +end + +local function setFG( colour ) + return function() + fg = colour + end +end + +local function setBG( colour ) + return function() + bg = colour + end +end + +local Escapes = { + m = { + [ "0" ] = function() + bold = false + fg = 7 + bg = 0 + end, + + [ "1" ] = function() + bold = true + end, + + [ "30" ] = setFG( 0 ), + [ "31" ] = setFG( 1 ), + [ "32" ] = setFG( 2 ), + [ "33" ] = setFG( 3 ), + [ "34" ] = setFG( 4 ), + [ "35" ] = setFG( 5 ), + [ "36" ] = setFG( 6 ), + [ "37" ] = setFG( 7 ), + + [ "40" ] = setBG( 0 ), + [ "41" ] = setBG( 1 ), + [ "42" ] = setBG( 2 ), + [ "43" ] = setBG( 3 ), + [ "44" ] = setBG( 4 ), + [ "45" ] = setBG( 5 ), + [ "46" ] = setBG( 6 ), + [ "47" ] = setBG( 7 ), + }, +} + +local function printPendingInputs() + if lastWasChat then + mud.newlineMain() + + lastWasChat = false + end + + for i = 1, #pendingInputs do + mud.printr( pendingInputs[ i ] ) + + lastWasGA = false + end + + pendingInputs = { } +end + +local function handleChat( message ) + lastWasChat = true + lastWasGA = false + + if not message then + return + end + + local oldFG = fg + local oldBG = bg + local oldBold = bold + + fg = 1 + bg = 0 + bold = true + + local noAnsi = message:gsub( "\27%[[%d;]*%a", "" ) + + action.doChatPreActions( noAnsi ) + action.doChatAnsiPreActions( message ) + + mud.newlineMain() + mud.newlineChat() + + for line, newLine in message:gmatch( "([^\n]*)(\n?)" ) do + for text, opts, escape in ( line .. "\27[m" ):gmatch( "(.-)\27%[([%d;]*)(%a)" ) do + if text ~= "" then + mud.printMain( text, fg, bg, bold ) + mud.printChat( text, fg, bg, bold ) + end + + for opt in opts:gmatch( "([^;]+)" ) do + if Escapes[ escape ][ opt ] then + Escapes[ escape ][ opt ]() + end + end + end + + if newLine == "\n" then + mud.newlineMain() + mud.newlineChat() + end + end + + mud.drawMain() + mud.drawChat() + + action.doChatActions( noAnsi ) + action.doChatAnsiActions( message ) + + fg = oldFG + bg = oldBG + bold = oldBold +end + +local function handleData( data ) + receiving = true + + dataBuffer = dataBuffer .. data + + if data:match( GA ) then + dataBuffer = dataBuffer:gsub( "\r", "" ) + + dataBuffer = dataBuffer:gsub( "\255\252\1", echoOn ) + dataBuffer = dataBuffer:gsub( "\255\251\1", echoOff ) + + dataBuffer = dataBuffer:gsub( "\255\253\24", "" ) + dataBuffer = dataBuffer:gsub( "\255\253\31", "" ) + dataBuffer = dataBuffer:gsub( "\255\253\34", "" ) + + dataBuffer = dataBuffer .. "\n" + + for line in dataBuffer:gmatch( "([^\n]*)\n" ) do + if lastWasGA then + if line ~= "" then + mud.newlineMain() + end + + lastWasGA = false + end + + if lastWasChat then + mud.newlineMain() + + lastWasChat = false + end + + local clean, subs = line:gsub( GA, "" ) + local hasGA = subs ~= 0 + + local noAnsi = clean:gsub( "\27%[%d*%a", "" ) + + local gagged = gag.doGags( noAnsi ) or gag.doAnsiGags( clean ) + + action.doPreActions( noAnsi ) + action.doAnsiPreActions( clean ) + + local subbed = sub.doSubs( clean ) + + for text, opts, escape in ( subbed .. "\27[m" ):gmatch( "(.-)\27%[([%d;]*)(%a)" ) do + if text ~= "" and not gagged then + mud.printMain( text, fg, bg, bold ) + end + + for opt in opts:gmatch( "([^;]+)" ) do + if Escapes[ escape ][ opt ] then + Escapes[ escape ][ opt ]() + end + end + end + + if hasGA then + lastWasGA = true + printPendingInputs() + else + if not gagged then + mud.newlineMain() + end + end + + action.doActions( noAnsi ) + action.doAnsiActions( clean ) + end + + mud.drawMain() + + dataBuffer = "" + receiving = false + end +end + +local function handleCommand( input, hide ) + if not alias.doAlias( input ) then + if not mud.connected then + mud.print( "\n#s> You're not connected..." ) + + return + end + + intercept.doIntercept( input ) + + if not hide and input ~= "" then + local toShow = ( showInput and input or ( "*" ):rep( input:len() ) ) .. "\n" + + if receiving then + table.insert( pendingInputs, toShow ) + else + if lastWasChat then + mud.newlineMain() + + lastWasChat = false + end + + lastWasGA = false + + mud.printr( toShow ) + end + end + + mud.send( input .. "\n" ) + mud.drawMain() + end +end + +local function handleInput( input, display ) + for command in ( input .. ";" ):gmatch( "([^;]*);" ) do + handleCommand( command, display ) + end +end + +mud.input = handleInput + +local function handleMacro( key, shift, ctrl, alt ) + if shift then + key = "s" .. key + end + + if ctrl then + key = "c" .. key + end + + if alt then + key = "a" .. key + end + + macro.doMacro( key ) +end + +local function handleClose() + ev.Loop.default:unloop() + + mud.event( "shutdown" ) +end + +return { + data = handleData, + chat = handleChat, + input = handleInput, + macro = handleMacro, + interval = interval.doIntervals, + close = handleClose, +} diff --git a/src/lua/intercept.lua b/src/lua/intercept.lua @@ -0,0 +1,43 @@ +local Intercepts = { } + +local function doIntercept( command ) + for i = 1, #Intercepts do + local intercept = Intercepts[ i ] + + if intercept.enabled then + local ok, err = pcall( string.gsub, command, intercept.pattern, intercept.callback ) + + if not ok then + mud.print( debug.traceback( "\n#s> intercept callback failed: %s" % err ) ) + end + end + end +end + +function mud.intercept( pattern, callback, disabled ) + enforce( pattern, "pattern", "string" ) + enforce( callback, "callback", "function" ) + enforce( disabled, "disabled", "boolean", "nil" ) + + local intercept = { + pattern = pattern, + callback = callback, + + enabled = not disabled, + + enable = function( self ) + self.enabled = true + end, + disable = function( self ) + self.enabled = false + end, + } + + table.insert( Intercepts, intercept ) + + return intercept +end + +return { + doIntercept = doIntercept, +} diff --git a/src/lua/interval.lua b/src/lua/interval.lua @@ -0,0 +1,79 @@ +local Intervals = { } + +local function doIntervals( loop, watcher ) + local now = loop:update_now() + + for i = 1, #Intervals do + local event = Intervals[ i ] + + if event.enabled then + event:checkTick( now ) + end + end +end + +function mud.now() + return ev.Loop.default:update_now() +end + +function mud.interval( callback, interval, disabled ) + enforce( callback, "callback", "function" ) + enforce( interval, "interval", "number" ) + + local event = { + callback = callback, + interval = interval, + nextTick = mud.now(), + + enabled = not disabled, + + enable = function( self ) + self.enabled = true + end, + disable = function( self ) + self.enabled = false + end, + + tick = function( self, now ) + callback( now ) + + self.nextTick = now + self.interval + end, + + checkTick = function( self, now ) + if self.nextTick == -1 then + self.nextTick = now + end + + if now >= self.nextTick then + self:tick( now ) + end + end, + + tend = function( self, tolerance, desired ) + tolerance = tolerance or 0 + desired = desired or mud.now() + + local toTick = ( self.nextTick - desired ) % self.interval + local sinceTick = self.interval - toTick + + if toTick <= tolerance then + self.nextTick = math.avg( self.nextTick, desired ) + elseif sinceTick <= tolerance then + self.nextTick = math.avg( self.nextTick, desired + self.interval ) + else + self.nextTick = desired + + self:checkTick( mud.now() ) + end + end, + } + + table.insert( Intervals, event ) + + return event +end + +return { + doIntervals = doIntervals, +} diff --git a/src/lua/macro.lua b/src/lua/macro.lua @@ -0,0 +1,31 @@ +local Macros = { } + +local function doMacro( key ) + local macro = Macros[ key ] + + if macro then + macro() + end +end + +function mud.macro( key, callback, disabled ) + enforce( key, "key", "string" ) + enforce( callback, "callback", "function", "string" ) + + if type( callback ) == "string" then + local command = callback + + callback = function() + mud.input( command ) + end + end + + assert( not Macros[ key ], "macro `%s' already registered" % key ) + + + Macros[ key ] = callback +end + +return { + doMacro = doMacro, +} diff --git a/src/lua/main.lua b/src/lua/main.lua @@ -0,0 +1,69 @@ +mud = { + connected = false, +} + +-- local function luarocks_path( opt, orig ) +-- local pipe = io.popen( "luarocks path " .. opt, "r" ) +-- if not pipe then +-- return nil +-- end +-- +-- local contents = pipe:read( "*all" ) +-- pipe:close() +-- +-- if not contents then +-- return orig +-- end +-- +-- return contents:gsub( "%s*$", "" ) .. ";" .. orig +-- end +-- +-- package.path = luarocks_path( "--lr-path", package.path ) +-- package.cpath = luarocks_path( "--lr-cpath", package.cpath ) + +table.unpack = table.unpack or unpack + +require( "utils" ) + +socket = require( "socket" ) +ev = require( "ev" ) + +require( "event" ) + +local script = require( "script" ) +local handlers = require( "handlers" ) + +require( "chat" ).init( handlers.chat ) +require( "connect" ).init( handlers.data ) + +local xFD, handleXEvents, + printMain, newlineMain, drawMain, + printChat, newlineChat, drawChat, + setHandlers, urgent, setStatus = ... + +mud.printMain = printMain +mud.newlineMain = newlineMain +mud.drawMain = drawMain + +mud.printChat = printChat +mud.newlineChat = newlineChat +mud.drawChat = drawChat + +mud.urgent = urgent + +require( "status" ).init( setStatus ) + +setHandlers( handlers.input, handlers.macro, handlers.close ) + +local loop = ev.Loop.default + +mud.handleXEvents = handleXEvents + +ev.IO.new( handleXEvents, xFD, ev.READ ):start( loop ) +ev.Timer.new( handlers.interval, 0.5, 0.5 ):start( loop ) + +script.load() + +loop:loop() + +script.close() diff --git a/src/lua/script.lua b/src/lua/script.lua @@ -0,0 +1,143 @@ +local lfs = require( "lfs" ) +local serialize = require( "serialize" ) + +local ScriptsDir = os.getenv( "HOME" ) .. "/.mudgangster/scripts" + +package.path = package.path .. ";" .. ScriptsDir .. "/?.lua" + +local function loadScript( name, path, padding ) + local function throw( err ) + if err:match( "^" .. path ) then + err = err:sub( path:len() + 2 ) + end + + mud.print( "#lr%-" .. padding .. "s", name ) + mud.print( "#s\n>\n> %s\n>", err ) + end + + mud.print( "#s\n> " ) + + local mainPath = path .. "/main.lua" + local readable, err = io.readable( mainPath ) + + if not readable then + throw( err ) + + return + end + + local env = setmetatable( + { + require = function( requirePath, ... ) + return require( "%s.%s" % { name, requirePath }, ... ) + end, + + saved = function( defaults ) + local settingsPath = path .. "/settings.lua" + + mud.listen( "shutdown", function() + local file = io.open( settingsPath, "w" ) + + file:write( serialize.unwrapped( defaults ) ) + + file:close() + end ) + + local settingsEnv = setmetatable( { }, { + __newindex = defaults, + } ) + + local settings = loadfile( settingsPath, "t", settingsEnv ) + + if not settings then + return defaults + end + + if _VERSION == "Lua 5.1" then + setfenv( settings, settingsEnv ) + end + + pcall( settings ) + + return defaults + end, + }, + { + __index = _G, + __newindex = _G, + } + ) + + local script, err = loadfile( mainPath, "t", env ) + + if not script then + throw( err ) + + return + end + + if _VERSION == "Lua 5.1" then + setfenv( script, env ) + end + + local loaded, err = pcall( script ) + + if loaded then + mud.print( "#lg%s", name ) + else + throw( err ) + end +end + +local function loadScripts() + mud.print( "#s> Loading scripts..." ) + + local readable, err = io.readable( ScriptsDir ) + + if readable then + local attr = lfs.attributes( ScriptsDir ) + + if attr.mode == "directory" then + local scripts = { } + local maxLen = 0 + + for script in lfs.dir( ScriptsDir ) do + if not script:match( "^%." ) then + local path = ScriptsDir .. "/" .. script + local scriptAttr = lfs.attributes( path ) + + if scriptAttr.mode == "directory" then + maxLen = math.max( script:len(), maxLen ) + + table.insert( scripts, { + name = script, + path = path, + } ) + end + end + end + + table.sort( scripts, function( a, b ) + return a.name < b.name + end ) + + for _, script in ipairs( scripts ) do + loadScript( script.name, script.path, maxLen ) + end + else + mud.print( "#sfailed!\n> `%s' isn't a directory", ScriptsDir ) + end + else + mud.print( "#sfailed!\n> %s", err ) + end + + mud.event( "scriptsLoaded" ) +end + +local function closeScripts() +end + +return { + load = loadScripts, + close = closeScripts, +} diff --git a/src/lua/serialize.lua b/src/lua/serialize.lua @@ -0,0 +1,56 @@ +-- can't handle cycles, only works on strings/numbers/bools/tables + +local function formatKey( key ) + if type( key ) == "string" then + return "[ %q ]" % key + end + + return "[ %s ]" % tostring( key ) +end + +local function serializeObject( obj ) + local t = type( obj ) + + if t == "number" or t == "boolean" then + return tostring( obj ) + end + + if t == "string" then + return "%q" % obj + end + + if t == "table" then + local output = "{ " + + for k, v in pairs( obj ) do + output = output .. "%s = %s, " % { formatKey( k ), serializeObject( v ) } + end + + return output .. "}" + end + + error( "I don't know how to serialize type " .. t ) +end + +local function serialize( obj ) + if not obj then + return "return { }" + end + + return "return " .. serializeObject( obj ) +end + +local function serializeUnwrapped( obj ) + local output = "" + + for k, v in pairs( obj ) do + output = output .. "%s = %s\n" % { k, serializeObject( v ) } + end + + return output +end + +return { + wrapped = serialize, + unwrapped = serializeUnwrapped, +} diff --git a/src/lua/status.lua b/src/lua/status.lua @@ -0,0 +1,67 @@ +local setStatus +local barItems = { } + +local function drawBar() + local status = { } + + for _, item in ipairs( barItems ) do + local fg = 7 + local bold = false + + for text, opts, escape in ( item.text .. "\27[m" ):gmatch( "(.-)\27%[([%d;]*)(%a)" ) do + if text ~= "" then + table.insert( status, { + text = text, + fg = fg, + bold = bold, + } ) + end + + for opt in opts:gmatch( "([^;]+)" ) do + if opt == "0" then + fg = 7 + bold = false + elseif opt == "1" then + bold = true + else + opt = tonumber( opt ) + + if opt and opt >= 30 and opt <= 37 then + fg = opt - 30 + end + end + end + end + end + + setStatus( status ) + + mud.handleXEvents() +end + +function mud.bar( priority ) + local item = { + priority = priority, + text = "", + + set = function( self, form, ... ) + enforce( form, "form", "string" ) + + self.text = string.parseColours( form:format( ... ) ) + drawBar() + end, + } + + table.insert( barItems, item ) + table.sort( barItems, function( a, b ) + return a.priority < b.priority + end ) + + return item +end + +return { + init = function( set ) + setStatus = set + end, +} diff --git a/src/lua/sub.lua b/src/lua/sub.lua @@ -0,0 +1,44 @@ +local Subs = { } + +local function doSubs( line ) + for i = 1, #Subs do + local sub = Subs[ i ] + + if sub.enabled then + local newLine, subs = line:gsub( sub.pattern, sub.replacement ) + + if subs ~= 0 then + return newLine + end + end + end + + return line +end + +function mud.sub( pattern, replacement, disabled ) + enforce( pattern, "pattern", "string" ) + enforce( replacement, "replacement", "string", "function" ) + + local sub = { + pattern = pattern, + replacement = replacement, + + enabled = not disabled, + + enable = function( self ) + self.enabled = true + end, + disable = function( self ) + self.enabled = false + end, + } + + table.insert( Subs, sub ) + + return sub +end + +return { + doSubs = doSubs, +} diff --git a/src/lua/utils.lua b/src/lua/utils.lua @@ -0,0 +1,224 @@ +getmetatable( "" ).__mod = function( self, form ) + if type( form ) == "table" then + return self:format( table.unpack( form ) ) + end + + return self:format( form ) +end + +function string.plural( count, plur, sing ) + return count == 1 and ( sing or "" ) or ( plur or "s" ) +end + +function string.startsWith( self, needle, ignoreCase ) + if ignoreCase then + return self:lower():sub( 1, needle:len() ) == needle:lower() + end + + return self:sub( 1, needle:len() ) == needle +end + +function string.commas( num ) + num = tonumber( num ) + + local out = "" + + while num >= 1000 do + out = ( ",%03d%s" ):format( num % 1000, out ) + + num = math.floor( num / 1000 ) + end + + return tostring( num ) .. out +end + +function math.round( num ) + return math.floor( num + 0.5 ) +end + +function math.avg( a, b ) + return ( a + b ) / 2 +end + +function io.readable( path ) + local file, err = io.open( path, "r" ) + + if not file then + return false, err + end + + io.close( file ) + + return true +end + +function enforce( var, name, ... ) + local acceptable = { ... } + local ok = false + + for _, accept in ipairs( acceptable ) do + if type( var ) == accept then + ok = true + + break + end + end + + if not ok then + error( "argument `%s' to %s should be of type %s (got %s)" % { name, debug.getinfo( 2, "n" ).name, table.concat( acceptable, " or " ), type( var ) }, 3 ) + end +end + +local function genericPrint( message, main, chat ) + local bold = false + local fg = 7 + local bg = 0 + + local function setFG( colour ) + return function() + fg = colour + end + end + + local Sequences = { + d = setFG( 7 ), + k = setFG( 0 ), + r = setFG( 1 ), + g = setFG( 2 ), + y = setFG( 3 ), + b = setFG( 4 ), + m = setFG( 5 ), + c = setFG( 6 ), + w = setFG( 7 ), + s = setFG( 8 ), + } + + for line, newLine in message:gmatch( "([^\n]*)(\n?)" ) do + for text, hashPos, light, colour, colourPos in ( line .. "#a" ):gmatch( "(.-)()#(l?)(%l)()" ) do + if main then + mud.printMain( text:gsub( "##", "#" ), fg, bg, bold ) + end + + if chat then + mud.printChat( text:gsub( "##", "#" ), fg, bg, bold ) + end + + if line:sub( hashPos - 1, hashPos - 1 ) ~= "#" then + if colourPos ~= line:len() + 3 then + assert( Sequences[ colour ], "invalid colour sequence: #" .. light .. colour ) + + bold = light == "l" + Sequences[ colour ]() + end + else + if main then + mud.printMain( light .. colour, fg, bg, bold ) + end + + if chat then + mud.printChat( light .. colour, fg, bg, bold ) + end + end + end + + if newLine ~= "" then + if main then + mud.newlineMain() + end + + if chat then + mud.newlineChat() + end + end + end + + if main then + mud.drawMain() + end + + if chat then + mud.drawChat() + end + + mud.handleXEvents() +end + +function mud.print( form, ... ) + genericPrint( form:format( ... ), true, false ) +end + +function mud.printc( form, ... ) + genericPrint( form:format( ... ), false, true ) +end + +function mud.printb( form, ... ) + genericPrint( form:format( ... ), true, true ) +end + +function mud.printr( str, fg, bg, bold ) + fg = fg or 7 + bg = bg or 0 + bold = bold or false + + for line, newLine in str:gmatch( "([^\n]*)(\n?)" ) do + mud.printMain( line, fg, bg, bold ) + + if newLine ~= "" then + mud.newlineMain() + end + end + + mud.drawMain() +end + +function mud.enable( ... ) + local things = { ... } + + if not things[ 1 ].enable then + things = things[ 1 ] + end + + for _, thing in ipairs( things ) do + thing:enable() + end +end + +function mud.disable( ... ) + local things = { ... } + + if not things[ 1 ].enable then + things = things[ 1 ] + end + + for _, thing in ipairs( things ) do + thing:disable() + end +end + +local ColourSequences = { + d = 0, + k = 30, + r = 31, + g = 32, + y = 33, + b = 34, + m = 35, + c = 36, + w = 37, +} + +function string.parseColours( message ) + message = assert( tostring( message ) ) + + return ( message:gsub( "()#(l?)(%l)", function( patternPos, bold, sequence ) + if message:sub( patternPos - 1, patternPos - 1 ) == "#" then + return + end + + if bold == "l" then + return "\27[1m\27[%dm" % { ColourSequences[ sequence ] } + end + + return "\27[0m\27[%dm" % { ColourSequences[ sequence ] } + end ):gsub( "##", "#" ) ) +end diff --git a/src/script.cc b/src/script.cc @@ -13,6 +13,10 @@ #define luaL_len lua_objlen #endif +static const uint8_t lua_bytecode[] = { +#include "build/lua_bytecode.h" +}; + static lua_State * lua; static int inputHandlerIdx = LUA_NOREF; @@ -176,7 +180,7 @@ void script_init() { lua_getfield( lua, -1, "traceback" ); lua_remove( lua, -2 ); - if( luaL_loadfile( lua, "main.lua" ) ) { + if( luaL_loadbufferx( lua, ( const char * ) lua_bytecode, sizeof( lua_bytecode ), "main", "b" ) != LUA_OK ) { printf( "Error reading main.lua: %s\n", lua_tostring( lua, -1 ) ); exit( 1 ); diff --git a/status.lua b/status.lua @@ -1,67 +0,0 @@ -local setStatus -local barItems = { } - -local function drawBar() - local status = { } - - for _, item in ipairs( barItems ) do - local fg = 7 - local bold = false - - for text, opts, escape in ( item.text .. "\27[m" ):gmatch( "(.-)\27%[([%d;]*)(%a)" ) do - if text ~= "" then - table.insert( status, { - text = text, - fg = fg, - bold = bold, - } ) - end - - for opt in opts:gmatch( "([^;]+)" ) do - if opt == "0" then - fg = 7 - bold = false - elseif opt == "1" then - bold = true - else - opt = tonumber( opt ) - - if opt and opt >= 30 and opt <= 37 then - fg = opt - 30 - end - end - end - end - end - - setStatus( status ) - - mud.handleXEvents() -end - -function mud.bar( priority ) - local item = { - priority = priority, - text = "", - - set = function( self, form, ... ) - enforce( form, "form", "string" ) - - self.text = string.parseColours( form:format( ... ) ) - drawBar() - end, - } - - table.insert( barItems, item ) - table.sort( barItems, function( a, b ) - return a.priority < b.priority - end ) - - return item -end - -return { - init = function( set ) - setStatus = set - end, -} diff --git a/sub.lua b/sub.lua @@ -1,44 +0,0 @@ -local Subs = { } - -local function doSubs( line ) - for i = 1, #Subs do - local sub = Subs[ i ] - - if sub.enabled then - local newLine, subs = line:gsub( sub.pattern, sub.replacement ) - - if subs ~= 0 then - return newLine - end - end - end - - return line -end - -function mud.sub( pattern, replacement, disabled ) - enforce( pattern, "pattern", "string" ) - enforce( replacement, "replacement", "string", "function" ) - - local sub = { - pattern = pattern, - replacement = replacement, - - enabled = not disabled, - - enable = function( self ) - self.enabled = true - end, - disable = function( self ) - self.enabled = false - end, - } - - table.insert( Subs, sub ) - - return sub -end - -return { - doSubs = doSubs, -} diff --git a/utils.lua b/utils.lua @@ -1,224 +0,0 @@ -getmetatable( "" ).__mod = function( self, form ) - if type( form ) == "table" then - return self:format( table.unpack( form ) ) - end - - return self:format( form ) -end - -function string.plural( count, plur, sing ) - return count == 1 and ( sing or "" ) or ( plur or "s" ) -end - -function string.startsWith( self, needle, ignoreCase ) - if ignoreCase then - return self:lower():sub( 1, needle:len() ) == needle:lower() - end - - return self:sub( 1, needle:len() ) == needle -end - -function string.commas( num ) - num = tonumber( num ) - - local out = "" - - while num >= 1000 do - out = ( ",%03d%s" ):format( num % 1000, out ) - - num = math.floor( num / 1000 ) - end - - return tostring( num ) .. out -end - -function math.round( num ) - return math.floor( num + 0.5 ) -end - -function math.avg( a, b ) - return ( a + b ) / 2 -end - -function io.readable( path ) - local file, err = io.open( path, "r" ) - - if not file then - return false, err - end - - io.close( file ) - - return true -end - -function enforce( var, name, ... ) - local acceptable = { ... } - local ok = false - - for _, accept in ipairs( acceptable ) do - if type( var ) == accept then - ok = true - - break - end - end - - if not ok then - error( "argument `%s' to %s should be of type %s (got %s)" % { name, debug.getinfo( 2, "n" ).name, table.concat( acceptable, " or " ), type( var ) }, 3 ) - end -end - -local function genericPrint( message, main, chat ) - local bold = false - local fg = 7 - local bg = 0 - - local function setFG( colour ) - return function() - fg = colour - end - end - - local Sequences = { - d = setFG( 7 ), - k = setFG( 0 ), - r = setFG( 1 ), - g = setFG( 2 ), - y = setFG( 3 ), - b = setFG( 4 ), - m = setFG( 5 ), - c = setFG( 6 ), - w = setFG( 7 ), - s = setFG( 8 ), - } - - for line, newLine in message:gmatch( "([^\n]*)(\n?)" ) do - for text, hashPos, light, colour, colourPos in ( line .. "#a" ):gmatch( "(.-)()#(l?)(%l)()" ) do - if main then - mud.printMain( text:gsub( "##", "#" ), fg, bg, bold ) - end - - if chat then - mud.printChat( text:gsub( "##", "#" ), fg, bg, bold ) - end - - if line:sub( hashPos - 1, hashPos - 1 ) ~= "#" then - if colourPos ~= line:len() + 3 then - assert( Sequences[ colour ], "invalid colour sequence: #" .. light .. colour ) - - bold = light == "l" - Sequences[ colour ]() - end - else - if main then - mud.printMain( light .. colour, fg, bg, bold ) - end - - if chat then - mud.printChat( light .. colour, fg, bg, bold ) - end - end - end - - if newLine ~= "" then - if main then - mud.newlineMain() - end - - if chat then - mud.newlineChat() - end - end - end - - if main then - mud.drawMain() - end - - if chat then - mud.drawChat() - end - - mud.handleXEvents() -end - -function mud.print( form, ... ) - genericPrint( form:format( ... ), true, false ) -end - -function mud.printc( form, ... ) - genericPrint( form:format( ... ), false, true ) -end - -function mud.printb( form, ... ) - genericPrint( form:format( ... ), true, true ) -end - -function mud.printr( str, fg, bg, bold ) - fg = fg or 7 - bg = bg or 0 - bold = bold or false - - for line, newLine in str:gmatch( "([^\n]*)(\n?)" ) do - mud.printMain( line, fg, bg, bold ) - - if newLine ~= "" then - mud.newlineMain() - end - end - - mud.drawMain() -end - -function mud.enable( ... ) - local things = { ... } - - if not things[ 1 ].enable then - things = things[ 1 ] - end - - for _, thing in ipairs( things ) do - thing:enable() - end -end - -function mud.disable( ... ) - local things = { ... } - - if not things[ 1 ].enable then - things = things[ 1 ] - end - - for _, thing in ipairs( things ) do - thing:disable() - end -end - -local ColourSequences = { - d = 0, - k = 30, - r = 31, - g = 32, - y = 33, - b = 34, - m = 35, - c = 36, - w = 37, -} - -function string.parseColours( message ) - message = assert( tostring( message ) ) - - return ( message:gsub( "()#(l?)(%l)", function( patternPos, bold, sequence ) - if message:sub( patternPos - 1, patternPos - 1 ) == "#" then - return - end - - if bold == "l" then - return "\27[1m\27[%dm" % { ColourSequences[ sequence ] } - end - - return "\27[0m\27[%dm" % { ColourSequences[ sequence ] } - end ):gsub( "##", "#" ) ) -end