Play mud games? Using Mush Client to write robot? Let's see what I show you~
This is a framework I wrote for writing robot with Lua Script in Mush Client.
The framework enables you dealing with every action/command base on the message/event type, and do it in sequence, which means you needn't pay attention to enable or disable a trigger, and just focuing on the logical of your robot.
The idea of the framework is liking a Windows Message/Event Driven mode. A serial of events you can define, and handle them in a callback function. The key point is an event can be defined as triggered how many times within a period or infinite time.
Moreover, a command sender I created helps you send commands for repetition, delaying, or you can extend it to process some special commands by adding codes in Command:Send function. The command sender will ensure all of your commands sent in sequence according to the order you invoke the Command:Add function.
In order to use this framework to develop your robot, you can follow this way:
1. define an event you wanna handle.
2. create a trigger in mush to fire this event.
3. write a call back function for this event.
4. in the callback function, you are able to do some response by sending commands using command sender.
for example:
you can create a trigger like:
you can handle events like:
Listener.new():Register(Event.new("event1"), EventHandler1.new())
EventHander1 = class(Callback)
function EventHander1:Do(event, )
-- do something
cmdSender:Add("cmd1;#3 cmd2;@2;cmd3") -- #3 means repeat 3 times, @2 means delay 2 seconds
end
-- handle "event2" five times
Listener.new():Register(Event.new("event2, 0, 5"), EventHandler2.new())
EventHander2 = class(Callback)
function EventHander2:Do(event, )
-- do something
cmdSender:Add({"cmd4", "@3", "#2 cmd5"}) -- accept commands in a table
end
-- handle "event3" twice within 10 seconds. if it is not triggered twice within 10 seconds, a timeout event is sent.
Listener.new():Register(Event.new("event3, 10, 2"), EventHandler3.new())
EventHander3 = class(Callback)
function EventHander3:Do(event, )
-- do something
if (event.isTimeout) then
cmdSender:Add("cmd6")
else
cmdSender:Add({"cmd7", "cmd8"})
end
end
Here is the codes of this framework, copy it to your lua script file, then you can use it.
-- OO, implement class module
---------------------------------------------------------
local _class = {}
function class(super)
local class_type = {}
class_type.ctor = false
class_type.super = super
class_type.new =
function()
local obj = {}
do
local create
create =
function(c, )
if c.super then
create(c.super, )
end
if c.ctor then
c.ctor(obj, )
end
end
create(class_type, )
end
setmetatable(obj, { __index = _class[class_type] })
return obj
end
local vtbl = {}
_class[class_type] = vtbl
setmetatable(class_type, { __newindex =
function(t, k, v)
vtbl[k] = v
end
})
if super then
setmetatable(vtbl, { __index =
function(t,k)
local ret = _class[super][k]
vtbl[k] = ret
return ret
end
})
end
return class_type
end
-- event
--- type: type of an event
--- timeout: in a particular time(in seconds) didn't receive the event will fire a timeout event
--- times: the event will be triggered how many times, then will be self removed
---------------------------------------------------------
Event = class()
function Event:ctor(type, timeout, times)
self.type = type
if (timeout == nil and times == nil) then
-- if both timeout and times are not set, then can be triggered any times (set times to zero)
self.timeout = 0
self.times = 0
elseif (timeout ~= nil and times == nil) then
-- if timeout is set, times is not set, then can be trigger only once
self.timeout = timeout
self.times = 1
else
-- if both timeout and times are set, then can be trigger any times within timeout
self.timeout = timeout
self.times = times
end
self.isTimeout = false
self.triggered = 0
end
function Event:Reset()
self.isTimeout = false
self.triggered = 0
end
-- callback: callback function when receved an event
---------------------------------------------------------
Callback = class()
function Callback:ctor(insideFunc)
self.func = insideFunc
end
function Callback:Invoke(event, )
-- logging
helper:Print("Event:", event.type, " Timeout:", event.isTimeout, " Triggered:", event.triggered)
-- call handler
self:Do(event, )
end
function Callback:Do(event, )
helper:Print("Do Noting")
end
-- listener
---------------------------------------------------------
Listener = class()
function Listener:ctor()
self.id = CreateGUID()
end
function Listener:Register(event, callback)
assert(event.type ~= nil, "event type is nil")
self.event = event
self.callback = callback
-- create timer if has timeout
if (event.timeout ~= 0) then -- create timer using type as timer name
helper:AddTimer(self.event.type, self.event.timeout)
end
-- add self in listener list
dispatcher:AddListener(self)
end
function Listener:Remove()
assert(self.event ~= nil, "have to register event then remove it")
-- if has timer and the timer is not timeout, delete it
if (self.event.timeout ~= 0 and not self.event.isTimeout) then
helper:RemoveTimer(self.event.type)
end
-- remove self in listener list
dispatcher:RemoveListener(self)
end
function Listener:OnEvent()
-- add triggered times
self.event.triggered = self.event.triggered + 1
-- check if reach triggered times
if (self.event.times ~= 0 and self.event.triggered == self.event.times) then
self:Remove()
end
-- call back
self.callback:Invoke(self.event, )
end
function Listener:OnTimeout()
-- set isTimeout and call back
self.event.isTimeout = true
-- delete listener
self:Remove()
-- call back
self.callback:Invoke(self.event)
end
-- event dispatcher
---------------------------------------------------------
EventDispatcher = class()
function EventDispatcher:ctor()
self.listeners = {}
end
function EventDispatcher:AddListener(listener)
self.listeners[listener.id] = listener
end
function EventDispatcher:RemoveListener(listener)
self.listeners[listener.id] = nil
end
function EventDispatcher:IsListening(listener)
return (self.listeners[listener.id] ~= nil)
end
function EventDispatcher:Match(eventType)
local matchs = {}
for k, v in pairs (self.listeners) do
if (v.event.type == eventType) then
table.insert(matchs, v)
end
end
return matchs
end
function EventDispatcher:SendEvent(eventType, )
local matchs = self:Match(eventType)
if (#matchs ~= 0) then
for k, v in pairs (matchs) do
v:OnEvent()
end
end
end
function EventDispatcher:SendTimeout(timerName)
local matchs = self:Match(timerName)
if (#matchs ~= 0) then
for k, v in pairs (matchs) do
v:OnTimeout()
end
end
end
-- only one instance
dispatcher = EventDispatcher.new()
-- Helper
---------------------------------------------------------
Helper = class()
function Helper:ctor()
self.isPrint = false
self.cmds = {}
end
function Helper:Print()
if self.isPrint then
Note()
end
end
function Helper:AddTimer(name, interval)
local hours = math.floor(interval / 3600)
interval = interval - (hours * 3600)
local minutes = math.floor(interval / 60)
local seconds = interval - (minutes * 60)
local status = AddTimer (name, hours, minutes, seconds, "dispatcher:SendTimeout(\"" .. name .. "\")", timer_flag.OneShot + timer_flag.Temporary + timer_flag.Replace, "")
assert(status == error_code.eOK, "fail to create timer:" .. name)
SetTimerOption(name, "send_to", 12)
EnableTimer(name, true)
ResetTimer(name)
end
function Helper:ResetTimer(name, interval)
assert(IsTimer(name), "timer doesn't exist")
EnableTimer(name, false)
local hours = math.floor(interval / 3600)
interval = interval - (hours * 3600)
local minutes = math.floor(interval / 60)
local seconds = interval - (minutes * 60)
SetTimerOption(name, "hour", hours)
SetTimerOption(name, "minute", minutes)
SetTimerOption(name, "second", seconds)
EnableTimer(name, true)
ResetTimer(name)
end
function Helper:RemoveTimer(name)
EnableTimer(name, false)
DeleteTimer(name)
end
-- only one instance
helper = Helper.new()
-- Command
--- Repeat: #4 xx (repeat 4 times for command xx)
--- Delay: @3 (delay 3 seconds)
---------------------------------------------------------
Command = class()
function Command:ctor()
self.cmds = {}
self.isRunning = false
self.thread = nil
end
function Command:ToTable(cmds)
assert(type(cmds) == "string", "commands must be string type")
local retVal = {}
for k, v in pairs(utils.split(cmds, ";")) do
if (string.sub(v, 1, 1) == "#") then -- convert repeat command
local sb, se = string.find(v, "%s+")
assert(sb ~= nil and se ~= nil, "wrong repeat command format")
local times = tonumber(string.sub(v, 2, sb - 1))
local cmd = string.sub(v, se + 1)
for i = 1, times, 1 do
retVal[#retVal + 1] = cmd
end
else
retVal[#retVal + 1] = v
end
end
return retVal
end
function Command:Add(cmds)
if (type(cmds) == "string") then
cmds = self:ToTable(cmds)
end
assert(type(cmds) == "table", "commands must be string or table type")
-- add cmds
for k, v in pairs (cmds) do
self.cmds[#self.cmds + 1] = v
end
-- wakeup to process
self:Wakeup()
end
function Command:Clear()
self.cmds = {}
end
function Command:Wakeup()
if (self.thread == nil) then
cmdSender.thread = coroutine.create(cmdSender.Do)
end
if (not self.isRunning) then
coroutine.resume(self.thread)
end
end
function Command:Do()
while true do
local cmd = nil
if (#cmdSender.cmds ~= 0) then
cmd = cmdSender.cmds[1] -- pick cmd in queue
table.remove (cmdSender.cmds, 1) -- remove cmd in queue
end
if (cmd ~= nil) then
cmdSender.isRunning = true
if (string.sub(cmd, 1, 1) == "@") then
local interval = tonumber(string.sub(cmd, 2))
if (interval > 0) then
helper:Print("delay:", interval)
DoAfterSpecial(interval, "coroutine.resume(cmdSender.thread)", 12)
coroutine.yield()
end
else
cmdSender:Send(cmd)
end
else
cmdSender.isRunning = false
coroutine.yield()
end
end
end
function Command:Send(cmd)
helper:Print("cmd:", cmd)
if (IsAlias(cmd) == error_code.eOK) then
SendImmediate(GetAliasInfo(cmd, 2))
else
SendImmediate(cmd)
end
end
cmdSender = Command.new()