Custom Quick Queues

Share your scripts and packages with other Mudlet users.
Post Reply
Iocun
Posts: 174
Joined: Wed Dec 02, 2009 1:45 am

Custom Quick Queues

Post by Iocun »

Since there seem to be quite a few people who are used to the ZMud way of queuing commands with #wait (which in a sense -is- more intuitive than using tempTimers), I thought I'd share my own version of creating quick queues of commands.

My function sendQueue() is meant to provide a means to very quickly set up a variety of different queues. Its basic syntax is:
sendQueue({"first action", first condition, "second action", second condition, "third action", third condition, "fourth action"})
(Of course you can make this as long as you want; it doesn't have to stop after four actions.)

"first action" etc.: These are primarily things you wish to send to the mud, such as "eat bread" or "climb tree" or "north". You may, however, also put aliases in there. If you have an alias to kick a rat on the pattern ^kill$ you can also use "kill" as an action in the queue.

first condition etc.: These are the conditions that must be fulfilled before the next action is executed. Conditions can be one of three things:
1. A number: If you use a number for a condition, it is interpreted as a delay time between the action before it and the action after it. If you use the number 0 here, the next action will be executed immediately.
2. A string: If you use a string for a condition (e.g. "onBalance"), it is interpreted as an event that must be raised for the next action to execute.
3. A table: If you use a table consisting of exactly one string element for a condition (e.g. {"^You see a bird flying above your head\.$"}) it is interpreted as a regex trigger that must match for the next action to be called.

You can mix all those up however you wish. For example:
sendQueue({"north", 0, "northeast", 0, "climb tree", "onBalance", "jump down", {"You land on the floor."}, "dig hole", 5, "climb in hole"})

This means: Go north, then immediately northeast, then immediately climb a tree. Wait until you recover balance, then jump down. As soon as you see yourself landing on the floor, dig a hole, wait 5 seconds then climb into the hole.

Note: It is important that you -always- keep to the order action-condition-action-condition. The first and last item in the table must always be an action, and there must -always- be a condition between two actions.

I use this for a wide variety of different things. For example I have a group of speedwalking paths stored like this:
paths = {
tasurke = {"remove shackle", 0, "say duanatharan", 0, "/ne,w,3gne4n", "onSpeedwalkComplete", "wear shackle"},
northern_wilderness = {"remove shackle", 0, "say duanatharan", 0, "/nw,2s", "onSpeedwalkComplete", "worm warp", "onEquilibrium", "/2e", "onSpeedwalkComplete", "worm warp", "onEquilibrium", "enter wilderness", "onWilderness", "wear shackle"},
kawhe = {"remove shackle", 0, "say duanathar", 0, "/w,ne,n,ne", "onSpeedwalkComplete", "worm warp", "onEquilibrium", "/2s,w", 0, "wear shackle"}
}

then I can simply execute sendQueue(paths.tasurke) and it will execute the commands to get me there.

Or, I could use this to put up my defences:

sendQueue({"dc",0,"vc",0, "nightsight",0,"thirdeye", "onBalance", "selfishness",
"onEquilibrium", "lifevision", 0, "nightsight", "onEquilibrium", "deathsight",
"onEquilibrium", "scales"})

Here's the code:
Code: [show] | [select all] lua
sendQueueTable = sendQueueTable or {}

function sendQueue(...)
	local id
	if arg.n == 1 and type(arg[1]) == "table" then						--Create a new queue
		echo("Setting up a new queue.\n")
		id = #sendQueueTable + 1
		sendQueueTable[id] = {q=arg[1]}	
	elseif arg.n == 2 and sendQueueTable[arg[1]] then
		id = arg[1]
		if arg[2] == "abort" then 
			echo("Aborting this queue.\n")
			table.remove(sendQueueTable, id)
			return id
		elseif arg[2] == "pause" then
			echo("Pausing this queue.\n")
			sendQueueTable[id].paused = true
			return id
		elseif arg[2] == "continue" then
			echo("Continuing this queue.\n")
			sendQueueTable[id].paused = false
		elseif arg[2] == "next" then
			if sendQueueTable[id].paused then
				echo("Paused. Doing nothing.\n")
				return false
			else
				echo("Executing next queue item.\n")
			end
		else
			return false
		end
	elseif type(arg[1]) == "string" then 	--check if this event matches any current queues
		for i,v in ipairs(sendQueueTable) do
			if v.waitingfor == arg[1] then
				sendQueue(i, "next")
			end
		end
	else
		return false
	end

	if sendQueueTable[id].trigger then killTrigger(sendQueueTable[id].trigger) end
	if sendQueueTable[id].timer then killTimer(sendQueueTable[id].timer) end

	if #sendQueueTable[id].q > 0 then							--still have things in the queue
		expandAlias(sendQueueTable[id].q[1])					--execute the current action
		table.remove(sendQueueTable[id].q, 1)					--remove the action from the queue
		if #sendQueueTable[id].q > 0 then						--if I still have more things to do
			if type(sendQueueTable[id].q[1]) == "number" then	--next condition is a timeout
				if sendQueueTable[id].q[1]>0 then
					echo("Waiting "..sendQueueTable[id].q[1].." seconds.\n")
					sendQueueTable[id].timer = tempTimer(sendQueueTable[id].q[1], [[sendQueue(]]..id..[[, "next")]])
					table.remove(sendQueueTable[id].q, 1)
				else
					table.remove(sendQueueTable[id].q, 1)
					sendQueue(id, "next")
				end
			elseif type(sendQueueTable[id].q[1]) == "table" then	--next condition is a trigger
				echo("Waiting for regex pattern: "..sendQueueTable[id].q[1][1].."\n")
				sendQueueTable[id].trigger = tempRegexTrigger(sendQueueTable[id].q[1][1], [[sendQueue(]]..id..[[, "next")]])
				table.remove(sendQueueTable[id].q, 1)
			elseif type(sendQueueTable[id].q[1]) == "string" then	--next condition is an event
				echo("Waiting for event: "..sendQueueTable[id].q[1]..".\n")
				sendQueueTable[id].waitingfor = sendQueueTable[id].q[1]
				table.remove(sendQueueTable[id].q, 1)
			else
				echo("Unknown condition: Executing next action immediately.\n")
				table.remove(sendQueueTable[id].q, 1)
				sendQueue(id, "next")
			end
		else
			echo("Queue completed.\n")
			table.remove(sendQueueTable, id)
		end
	end
	return id
end
As you may notice upon studying the code, it provides also a few other ways of interacting with your queues.
First of all, every queue gets assigned an individual ID upon creation which it returns. Using this ID, you can influence how the queue is executed, using the following terms:

sendQueue(ID, "next") -> This executes the next action in the queue immediately, without waiting for the condition to be fulfilled.

sendQueue(ID, "abort") -> This aborts the whole queue and deletes it.

sendQueue(ID, "pause") -> This pauses the whole queue, meaning that even if the next condition is fulfilled it won't execute anything until you do:

sendQueue(ID, "continue") -> This continues the execution of a queue after pausing it.

P.S. In order to make this work with events as well, you will have to add all events you want to use with this as "User Defined Event Handlers" on top of the script.
Last edited by Iocun on Sat Sep 18, 2010 6:59 am, edited 1 time in total.

User avatar
Heiko
Site Admin
Posts: 1548
Joined: Wed Mar 11, 2009 6:26 pm

Re: Custom Quick Queues

Post by Heiko »

Without having looke in detail at your code: How generic is this? I'd like to include this sort of thing in LuaGlobal, but under a more suitable name like batchTimer() or something like that.

Iocun
Posts: 174
Joined: Wed Dec 02, 2009 1:45 am

Re: Custom Quick Queues

Post by Iocun »

Should be pretty generic; maybe some things can still be optimized for this though.

One main question however is what to actually use to execute the individual actions. Currently it works with expandAlias(). I used this to make the queues as simple as possible for just sending stuff to the mud directly, rather than, for instance, using loadstring(), which would then require using "send('something')" over simply doing 'something'. I used expandAlias() over send(), so that I still could use certain abbreviations, such as a speedwalking alias.

It -would- be more generic if it called functions with loadstring() instead of the expandAlias(), but it would make using it a bit less straight-forward. However, this is a really small and simple change to make either way.

One problem I see for including it in LuaGlobal is its use of events: Currently, if I want the sendQueue to work with a custom event of mine, say, "onBalance", I have to register "onBalance" as an event for that script item. I don't know how you would do something like that if it was in LuaGlobal.

I guess one could just include a timer-based version in LuaGlobal, that doesn't do things like waiting for events or triggers.

Lucky24
Posts: 52
Joined: Sun Sep 12, 2010 1:50 am

Re: Custom Quick Queues

Post by Lucky24 »

I needed a way to insure no commands were overwritten, so I used a lot of the basic ideas from your sendQueue() function to write my own serialized queuing system. I haven't fully tested it yet, and I'd like to add a few more features, but right now it will accept any number of commands and pauses, you can insert at the beginning of the list, or append at the end, and add additional commands at any time, and it should keep track of them all. I did this because when testing your sendQueue(), if multiple queues were started very shortly after each other, it seemed to get confused and lose commands. In addition, I needed a way to make sure *no* commands were sent faster than a given time period (in my case about 2 seconds).

The code is comprised of three functions:

queueAdd(command, pause) command to append to list, length of time to pause after command

queueInsert(command, pause) command to insert at beginning of list, length of time to pause after command

processQueue() starts executing the queue. Automatically invoked when queueAdd() or queueInsert() is run. If queue is already running, will ignore and let it run. Passing it the string "ABORT" will stop the queue and delete all queued commands.

NOTE: If you pass a command as a table {}, the function will parse it as lua code, in this case pass the code in [[ ]].

There's also a couple variables to set, which should probably be added to functions to change them. They are:

gQueue.defaultPauseLength -- if no pause length is specified when adding a command, this is the default pause after the command is sent

gQueue.startingPause --When starting the queue, how long to wait before sending the first command

Edit: fixed bug where if script is reloaded during processing, may not finish and thus leave timerID set. Now checks if the timer is active, instead of if the variable is assigned.
Code: [show] | [select all] lua
gQueue = gQueue or {}
gQueue.commands = gQueue.commands or {} --commands to execute
gQueue.pauses = gQueue.pauses or {} --length to pause for corresponding command
gQueue.timerID = gQueue.timerID or false --id of current timer
gQueue.defaultPauseLength = 2.5 --default pause after commands
gQueue.startingPause = 1 --if no commands in queue, wait how long b/f first?

-- Queue Add (Append) Command
function queueAdd(command,pause)
	table.insert(gQueue.commands,command)
	echo("\nAppended (Added) String to Queue: " .. command .. ". ") 
	if not pause then
		pause = gQueue.defaultPauseLength
	end
	table.insert(gQueue.pauses,pause)
	echo("Appended Pause to Queue: " .. pause .. "\n")
	queueProcess()
end

-- Queue Insert (Prepend) Command
function queueInsert(command)
	table.insert(gQueue.commands,1,command)
	echo("\nInserted (Prepended) String into Queue: " .. command .. ". ") 
	if not pause then
		pause = gQueue.defaultPauseLength
	end
	table.insert(gQueue.pauses,1,pause)
	echo("Inserted Pause into Queue: " .. pause .. "\n")
	queueProcess()
end

function processQueue(recurseLevel)
	local command = 0
	local pause = 0
	if recurseLevel==nil then
		if isActive(tostring(gQueue.timerID),"timer")==0 then
			echo("\nStarting Queue Processing")
			recurseLevel=1
	 		gQueue.timerID = tempTimer(gQueue.startingPause,[[processQueue(]]..recurseLevel..[[)]])
		else
			--echo("\nQueue Processing already in progress, so aborting...")
			return false
		end
	elseif recurseLevel=="ABORT" then
		echo("\nAborting All Queued Commands.")
		gQueue.commands = {}
		gQueue.pauses = {}
		killTimer(gQueue.timerID)
		gQueue.timerID = false
	elseif type(recurseLevel) == "number" then
		if gQueue.commands[1] and gQueue.pauses[1] then
			command = table.remove(gQueue.commands,1)
			pause = table.remove(gQueue.pauses,1)
			--echo("\nProcessing Queue level: " .. recurseLevel)
			if type(command) == "table" then
				echo("\nExecuting Queue as code (was given table): " .. command)
				tempTimer(0,command)
			elseif type(command) == "string" then
				echo("\nSending Queue as command: ")
				expandAlias(command)
			else
				echo("\nDunno what command you sent: " .. command)
			end
			recurseLevel=recurseLevel+1
			echo("\nPausing for "..pause.." seconds before next command.")
			gQueue.timerID = tempTimer(pause,[[processQueue(]]..recurseLevel..[[)]])
		elseif #gQueue.commands>0 then
			echo("\nUh oh...missing Queue somewhere...aborting. This is a bug?")
		else
			echo("\nFinished Processing Queue at level: " .. recurseLevel)
			gQueue.timerID = false
			return true
		end
	else 
		echo("\nUnknown processQueue() recurseLevel")
                return false
	end	
end

bluebaleen
Posts: 48
Joined: Sun Feb 06, 2011 2:00 pm

Re: Custom Quick Queues

Post by bluebaleen »

I am attempting to implement Iocun's sendQueue function and it seems that the function hangs on queue items that require a raised event for processing. I can use sendQueue(ID, "next") to forcibly advance the queue further but the events, themselves, are being ignored. Has anyone else had luck in successfully implementing this function? I am, admittedly, new to Lua so if there is a bug in the code it is not apparent to me. Thanks for your time!

Iocun
Posts: 174
Joined: Wed Dec 02, 2009 1:45 am

Re: Custom Quick Queues

Post by Iocun »

Have you registered those events at the top of the script?

You have to add any events you want to work with this next to "Add User Defined Event Handler" and click on the + button to the right of it, so you get something like:
Image
That's how it looks for me. Registered events include "onBalance", "onEqBal", "onSpeedwalkComplete", and some more.

bluebaleen
Posts: 48
Joined: Sun Feb 06, 2011 2:00 pm

Re: Custom Quick Queues

Post by bluebaleen »

That was the problem. Some how I managed to miss that requirement at the bottom of your post. I went back and caught it the second time. After correcting this issue via registering all of my events with the function event handler, I ran into an additional problem:

I track equilibrium and balance off of the prompt in my system. As such, I just added raiseEvent("onEquilibrium") and raiseEvent("onBalance") to my prompt trigger. This resulted in a rapid successive raising of events and caused the sendQueue function to process the entire queue at once before I actually lost equilibrium from the first eq-requiring command. I remedied this by simply creating a redundant tracking mechanism for eq/bal that uses triggers on the messages "You have recovered equilibrium." and "You have recovered balance." This just means that my haveBalance and haveEq variables get reset twice, which is no big deal.

Thank you for contributing this function to the community. I can see myself using it often. Also, thank you for your reply.

All the best

Post Reply