I just wrote up a small ATCP library. It seems to work quite well, and in a similar fashion to my original ATCP plugin.
Minor problem though, Nick: If I recall, you said that only the plugin that sent the DO gets called with "SENT_DO", right? I think that should be changed in favor of calling all plugins with SENT_DO. WILL would short-circuit at the first plugin to enable it, of course, but any number of other plugins might need to know if it's been enabled, and do their own setup relevant to the protocol. This is particularly an issue with the ATCP library, since it needs to be able to send its "hello" message to guarantee that those modules are enabled.
Here's what I have so far. It's probably not finished - I'd like to fix up the module infrastructure a bit - but it seems to work in testing.
local codes = {
IAC_DO_ATCP = "\255\253\200", -- enables ATCP communication
IAC_SB_ATCP = "\255\250\200", -- begins an ATCP packet
IAC_SE = "\255\240", -- ends an ATCP packet
ATCP = 200, -- ATCP protocol number
}
local hello_msg = "hello MUSHclient " .. Version() .. "\n" ..
"auth 1\n" ..
"char_name 1\n" ..
"char_vitals 1\n" ..
"room_brief 1\n" ..
"room_exits 1\n" ..
"map_display 1\n" ..
"composer 1\n" ..
"keepalive 1\n" ..
"topvote 1\n" ..
"ping 1\n"
local callbacks = setmetatable({}, {
__index = function(tbl, key)
local group = {}
rawset(tbl, key, group)
return group
end,
})
local Register = function(msg, callback)
if type(msg) ~= "string" then
error("Message name must be a string")
elseif type(callback) ~= "function" then
error("Callback must be a function")
end
table.insert(callbacks[msg], callback)
end
local Unregister = function(msg, callback)
if type(msg) ~= "string" then
error("Message name must be a string")
elseif type(callback) ~= "function" then
error("Callback must be a function")
end
for k,v in ipairs(callbacks[msg]) do
if v == callback then
table.remove(callbacks[msg], k)
break
end
end
end
local Startup = function(type, data)
if type == codes.ATCP then
if data == "WILL" then
return true
elseif data == "SENT_DO" then
SendPkt(codes.IAC_DO_ATCP .. codes.IAC_SB_ATCP .. hello_msg .. codes.IAC_SE)
return true
end
end
return false
end
local Handle = function(type, data)
if type ~= codes.ATCP then
return
end
local message, content
local separator = data:find("%s")
if separator then
message, content = data:sub(1, separator-1), data:sub(separator+1)
else
message, content = data, ""
end
for _,callback in ipairs(callbacks[message]) do
local ok, err = pcall(callback, message, content)
if not ok then
TraceOut("Error while executing ATCP callback " ..
tostring(callback) .. " for message " .. message ..
":\n " .. (err or ""))
end
end
-- send to catch-all handlers as well
for _,callback in ipairs(callbacks["*"]) do
local ok, err = pcall(callback, message, content)
if not ok then
TraceOut("Error while executing ATCP callback " ..
tostring(callback) .. " for message " .. message ..
":\n " .. (err or ""))
end
end
end
return {
Register = Register,
Unregister = Unregister,
Startup = Startup,
Handle = Handle,
}
Usage:
ATCP = require("atcp")
foo = function(message, content)
-- ...
end,
bar = function(message, content)
-- ...
end,
catchall = function(message, content)
-- ...
end
OnPluginInstall = function()
ATCP.Register("Char.Vitals", foo)
ATCP.Register("Room.Name", bar)
ATCP.Register("*", catchall) -- receives all messages
end
-- Set up ATCP handlers
OnPluginTelnetRequest = ATCP.Startup
OnPluginTelnetSubnegotiation = ATCP.Handle
-- Alternatively, if you want to handle these events yourself, too:
OnPluginTelnetRequest = function(type, data)
ATCP.Startup(type, data)
-- ...
end
OnPluginTelnetSubnegotiation = function(type, data)
ATCP.Handle(type, data)
-- ...
end
|