Welcome to the Achaea Forums! Please be sure to read the Forum Rules.

Client-side management of serverside queue - help wanted!

KlendathuKlendathu Eye of the StormMember Posts: 2,448 @@ - Legendary Achaean
I'm currently using Svof as my main system, with some of my own bits and pieces added on. I have a pretty effective, if a little stupid, dor routine which is faster than that in Svof, but Svof's queue management is far superior.

I looked into how WSys queue management works, but couldn't get it to translate out effectively.

Does anyone have a standalone queue management system they use for Mudlet which makes use of serverside queueing, so multiple items can be added to the queue, taken out of the queue, etc?

I'm not expecting anyone to just hand it over on a plate, happy to do some of the work myself. but reaching out in hope of some guidance.

Tharos, the Announcer of Delos shouts, "It's near the end of the egghunt and I still haven't figured out how to pronounce Clean-dat-hoo."

Comments

  • AntoniusAntonius Member Posts: 4,050 @@ - Legendary Achaean
    edited February 14
    I have one, which may do everything you need or may need some changes to meet your requirements. If you're willing to wait until I get home (about six hours) I'm happy to do a code dump of the entire thing. There'd be a few changes required - removing/changing the namespacing, changing the call to my echo function, and changing the command separator (mine is ||), off the top of my head - and you'd need to create the triggers, but it should be less work than writing it from scratch.

    Here's a quick(ish) rundown on some of the things my queueing system does:

    Limits the number of queued commands to the appropriate size for the specified queue (one for the CLASS queue, six for the others).

    I consider a command queued as soon as the function runs, and only use the trigger to verify that it has been queued, with a 1 second timer to cancel any commands that haven't actually been queued (due to lag, svof denying the command, amnesia, etc.), so even if I spam on my end I'm not spamming the game with unnecessary queue commands. Will also pick up actions from the QUEUE messages if it doesn't know about trying to queue them (but everything I have that uses queueing uses the script).

    The queue function (used for actually queueing stuff) has six parameters, off the top of my head:
    queue ("eq", "balance", "eqbal" or "class" plus some shorthands/alternatives that are hard-coded, like "voice" mapping to "class")
    position (add or prepend)
    command (string or table [will concatenate into a string using a command separator])
    usesbal (boolean)
    canprepend (boolean, I've never actually used this but coded it in just in case)
    repeatable (default is false if you just don't pass a value, and for most commands you don't want to repeat them, but some - like movement or getting items - you will)

    Can only have one balance using command queued at any one time in that queue, since there's no point trying to do more than one since the subsequent ones will fail. If you prepend, and it uses balance, it will by default refuse to do it (there's no point prepending a balance using command that will then stop everything else from happening); that's why the fifth parameter exists so you can in theory override this (but I don't know why you'd want to). If you add, and it uses balance, it will add it to the end of the queue OR replace the existing balance using command (which is always at the end of the queue).

    If you prepend a non-balance using command, it will just prepend it. If you add a non-balance using command, it will insert it before the queued balance using command (if there is one, otherwise it just goes on the end). So if I already have my attack alias queued (which uses balance), and my opponent leaves, I can queue up directions to move, and it will be inserted before my attack, so I'll move/attack, rather than trying to attack/move.

    Most commands aren't repeatable (there's no point in queueing up your attack alias six times, for example). However, the sixth parameter will allow you to say that it IS repeatable, so it will queue more than one of the same thing (I currently only use this for movement commands and GET/DROP/PUT).

    Cuts down on spam and commands sent to the game. Rather than mashing an alias that constantly clears/queues a command, it will instead constantly run the function that will decide if it needs to do anything at all.
    KlendathuInvictus
  • KlendathuKlendathu Eye of the StormMember Posts: 2,448 @@ - Legendary Achaean
    That would be absolutely fantastic. Can I wait six hours? Like a little kid on Christmas Eve!

    Tharos, the Announcer of Delos shouts, "It's near the end of the egghunt and I still haven't figured out how to pronounce Clean-dat-hoo."

  • InvictusInvictus Member Posts: 58 ✭✭ - Stalwart
    this is exactly what I've been trying to get working. 
  • AntoniusAntonius Member Posts: 4,050 @@ - Legendary Achaean

    Code:

    antonius.queueing = antonius.queueing or {}
    
    -- indexed tables (ordered arrays) containing the currently queued commands
    antonius.queueing.queues = antonius.queueing.queues or {
    	balance = {},
    	equilibrium = {},
    	eqbal = {},
    	class = {}
    }
    
    antonius.queueing.timers = antonius.queueing.timers or {
    	balance = nil,
    	equilibrium = nil,
    	eqbal = nil,
    	class = nil
    }
    
    -- check if a given command is queued in the current queue
    antonius.queueing.isqueued = function(queue, command)
    	local command = command:upper()
    	for _, c in ipairs(antonius.queueing.queues[queue]) do
    		if c.command == command then return true end
    	end
    	return false
    end
    
    antonius.queueing.getbalanceposition = function(queue)
    	for i, c in pairs(antonius.queueing.queues[queue]) do
    		if c.usesbal then
    			return i
    		end
    	end
    	return -1
    end
    
    antonius.queueing.getcommand = function(queue, position)
    	return antonius.queueing.queues[queue][position]
    end
    
    antonius.queueing.anyqueued = function(queue)
    	return #antonius.queueing.queues[queue] > 0
    end
    
    antonius.queueing.balancequeued = function(queue)
    	return antonius.queueing.getbalanceposition(queue) ~= -1
    end
    
    antonius.queueing.queuemap = {
    	bal = "balance",
    	eq = "equilibrium",
    	voice = "class"
    }
    
    --[[
    	queue: balance, equilibrium, eqbal or class
    	position: add or prepend
    --]]
    antonius.queueing.queue = function(queue, position, command, useseqbal, replaceeqbal, repeatable)
    	local queue = antonius.queueing.queuemap[queue] or queue
    	if not(queue == "balance" or queue == "equilibrium" or queue == "eqbal" or queue == "class") then
    		antonius.echo("Invalid queue " .. queue .. ".")
    		return
    	elseif #antonius.queueing.queues[queue] == 6 then
    		antonius.echo("Queue full: " .. queue .. ".")
    		return
    	end
    	if type(command) == "table" then command = table.concat(command, "||") end
    	--if ssc.inslowcuringmode() then send(command) return end
    
    	if not repeatable and antonius.queueing.isqueued(queue, command) then return end
    	if queue == "class" then
    		antonius.queueing.queues.class = {command}
    		send("queue add class " .. command, false)
    		return
    	end
    	
    	local commobj = {
    		command = command:upper(),
    		usesbal = useseqbal,
    		confirmed = false
    	}
    
    	if position == "prepend" then
    		-- don't want to prepend actions that use eq and/or balance
    		if useseqbal then return end
    		table.insert(antonius.queueing.queues[queue], 1, commobj)
    		send("queue " .. position .. " " .. queue .. " " .. command, false)
    	else
    		local balpos = antonius.queueing.getbalanceposition(queue)
    		local balcommand = antonius.queueing.getcommand(queue, balpos)
    		if useseqbal or replaceeqbal then
    			-- if we have one already queued, replace it
    			if balpos ~= -1 then
    				antonius.queueing.queues[queue][balpos] = commobj
    				send("queue replace " .. queue .. " " .. balpos .. " " .. command, false)
    			else
    			-- otherwise, just insert at the end
    				table.insert(antonius.queueing.queues[queue], commobj)
    				send("queue " .. position .. " " .. queue .. " " .. command, false)
    			end
    		else
    			-- if we don't have a balance using command queued, just add this to the end
    			if balpos == -1 then
    				table.insert(antonius.queueing.queues[queue], commobj)
    				send("queue " .. position .. " " .. queue .. " " .. command, false)
    			else
    			-- otherwise, we want to shift the balance using command up one
    				table.insert(antonius.queueing.queues[queue], balpos, commobj)
    				send("queue insert " .. queue .. " " .. balpos .. " " .. command, false)
    			end
    		end
    	end
    	
    	if antonius.queueing.timers[queue] then
    		killTimer(antonius.queueing.timers[queue])
    	end
    	antonius.queueing.timers[queue] = tempTimer(0.5, [[antonius.queueing.checkconfirm("]] .. queue .. [[")]])
    end
    
    antonius.queueing.unqueue = function(queue)
    	antonius.queueing.queues[queue] = {}
    end
    
    antonius.queueing.confirm = function(queue, command, position, index)
    	local command = command:upper()
    	local commandpresent = false
    	-- confirm the first instance of this command we find
    	for i, c in ipairs(antonius.queueing.queues[queue]) do
    		if c.command == command then
    			commandpresent = true
    			if	not c.confirmed then
    				c.confirmed = true
    				break
    			end
    		end
    	end
    	
    	-- if we found this command, confirmed or otherwise, we don't need to do anything else
    	-- if it was already confirmed, suggests a replace/add to shift up a balance using command
    	if commandpresent then return end
    	
    	-- if we didn't find any instance of the specified command, could be an ADD command from something other than the script
    	local commobj = {
    		command = command,
    		usesbal = position ~= "prepend", -- if not prepending, we assume it uses balance because we don't actually know
    		confirmed = true -- we can automatically confirm it because it's in reaction to game output
    	}
    	
    	if position == "insert" then
    		table.insert(antonius.queueing.queues[queue], index, commobj)
    	elseif position == "replace" then
    		antonius.queueing.queues[queue][index] = commobj
    	elseif position == "prepend" then
    		table.insert(antonius.queueing.queues[queue], 1, commobj)
    	else
    		table.insert(antonius.queueing.queues[queue], commobj)
    	end
    end
    
    antonius.queueing.checkconfirm = function(queue)
    	killTimer(antonius.queueing.timers[queue])
    	antonius.queueing.timers[queue] = nil
    	local indexes = {}
    	
    	for i, c in ipairs(antonius.queueing.queues[queue]) do
    		if not c.confirmed then
    			table.insert(indexes, i)
    		end
    	end
    	
    	for _, index in ipairs(indexes) do
    		table.remove(antonius.queueing.queues[queue], index)
    	end
    end
    

    Triggers (line delta is 0 for all multiline triggers):

    Name: Queued command ran
    Multiline: Yes
    Pattern: [System]: Running queued (begin of line)
    Pattern: ^\[System\]\: Running queued (\w+) command: (.+)$ (perl regex)
    Code: 
    antonius.queueing.unqueue(multimatches[2][2])
    
    Name: Added queued command
    Multiline: Yes
    Pattern: [System]: Added (begin of line)
    Pattern: ^\[System\]\: Added (.+) to your (\w+) queue\.$ (perl regex)
    Code: 
    local queue = multimatches[2][3]
    local command = multimatches[2][2]
    antonius.queueing.confirm(queue, command, "add")
    
    Name: Prepended queued command
    Multiline: Yes
    Pattern: [System]: Prepended (begin of line)
    Pattern: ^\[System\]\: Prepended (.+) to your (\w+) queue\.$ (perl regex)
    Code:
    local queue = multimatches[2][3]
    local command = multimatches[2][2]
    antonius.queueing.confirm(queue, command, "prepend")
    
    Name: Replaced queued command
    Multiline: Yes
    Pattern: [System]: replaced command (begin of line)
    Pattern: ^\[System\]: replaced command #(\d+) in the (\w+) queue with (.+)\.$ (perl regex)
    Code: 
    local queue = multimatches[2][3]
    local command = multimatches[2][4]
    antonius.queueing.confirm(queue, command, "replace", tonumber(multimatches[2][2]))
    
    Name: Inserted queued command
    Multiline: Yes
    Pattern: [System]: inserted (begin of line)
    Pattern: ^\[System\]: inserted(?:ed)? command #(\d+) in the (\w+) queue with (.+)\.$ (perl regex)
    Code: 
    local queue = multimatches[2][3]
    local command = multimatches[2][4]
    antonius.queueing.confirm(queue, command, "insert", tonumber(multimatches[2][2]))
    
    Name: Cleared all queues
    Multiline: No
    Pattern: Your queues are already empty. (exact match)
    Pattern: Cleared your queues. (exact match)
    Code: 
    antonius.queueing.queues = {
    	balance = {},
    	equilibrium = {},
    	eqbal = {},
    	class = {}
    }
    
    Name: Cleared queue
    Multiline: No
    Pattern: ^Your (\w+) queue is already empty\.$ (perl regex)
    Pattern: ^\[System\]\: Queued (\w+) commands cleared\.$ (perl regex)
    Pattern: ^Your (\w+) queue is empty\.$ (perl regex)
    Code:
    antonius.queueing.queues[matches[2]] = {}
    
    Valaria
  • KlendathuKlendathu Eye of the StormMember Posts: 2,448 @@ - Legendary Achaean
    You sir, are a gentlemen of the highest order.

    Tharos, the Announcer of Delos shouts, "It's near the end of the egghunt and I still haven't figured out how to pronounce Clean-dat-hoo."

    Kyrra
  • AntoniusAntonius Member Posts: 4,050 @@ - Legendary Achaean

    Couple of additional notes:

    If you just delete "antonius." from the entire thing, I think it should still work without any issues caused by that namespace not existing. The "||" will need to be changed to whatever your command separator happens to be. This line:

    --if ssc.inslowcuringmode() then send(command) return end

    is commented out because it calls a function from my own curing system, but the "ssc" part could easily be replaced with "svo" and then uncommenting the line would be fine.

  • KlendathuKlendathu Eye of the StormMember Posts: 2,448 @@ - Legendary Achaean
    edited February 15
    I've been working through this script, seeing where I can replace clunky "send queue add" bits and pieces in my own scripts, and have a question regarding replacing eqbal using commands with new ones (to do things like replacing attack with mountjump, as a bad example).
    Antonius said:
    Can only have one balance using command queued at any one time in that queue, since there's no point trying to do more than one since the subsequent ones will fail. If you prepend, and it uses balance, it will by default refuse to do it (there's no point prepending a balance using command that will then stop everything else from happening); that's why the fifth parameter exists so you can in theory override this (but I don't know why you'd want to). If you add, and it uses balance, it will add it to the end of the queue OR replace the existing balance using command (which is always at the end of the queue).

    If you prepend a non-balance using command, it will just prepend it. If you add a non-balance using command, it will insert it before the queued balance using command (if there is one, otherwise it just goes on the end). So if I already have my attack alias queued (which uses balance), and my opponent leaves, I can queue up directions to move, and it will be inserted before my attack, so I'll move/attack, rather than trying to attack/move.
    antonius.queueing.queue(queue, add or prepend, command, useseqbal, replaceeqbal, repeatable)
    To add to the queue, I simply do:
    antonius.queueing.queue("eqbal","add","mountjump "..dir,true)
    How do I get rid of any existing eqbal using command and replace it with the mountjump?



    Tharos, the Announcer of Delos shouts, "It's near the end of the egghunt and I still haven't figured out how to pronounce Clean-dat-hoo."

  • AntoniusAntonius Member Posts: 4,050 @@ - Legendary Achaean
    edited February 15
    @Klendathu That should happen by default if you pass true as the fourth (to indicate that this new command uses balance) or fifth (to indicate that you want to replace the balance using command, even if this one doesn't use it) parameter's value, as long as you said that your attack command/alias uses balance when queueing it using the function (anything it picks up from game output alone it will assume uses balance, because it doesn't know any better).

    The code that handles that is this section of the queue function:
    if useseqbal or replaceeqbal then
    	-- if we have one already queued, replace it
    	if balpos ~= -1 then
    		antonius.queueing.queues[queue][balpos] = commobj
    		send("queue replace " .. queue .. " " .. balpos .. " " .. command, false)
    	else
    	-- otherwise, just insert at the end
    		table.insert(antonius.queueing.queues[queue], commobj)
    		send("queue " .. position .. " " .. queue .. " " .. command, false)
    	end
    else
    

    So if you did antonius.queueing.queue("eqbal", "add", "kick Antonius", true); and then called antonius.queueing.queue("eqbal", "add", "mountjump northeast", true); before the kick had happened (i.e. it was still queued), it would replace the "kick Antonius" command with the "mountjump northeast" command.

    Klendathu
  • KlendathuKlendathu Eye of the StormMember Posts: 2,448 @@ - Legendary Achaean
    OK, that makes perfect sense, and was what I thought was happening.

    Next question, is it possible, with the script as it exists now, to add multiple entries which consume balance and it to work through them as balance is recovered?

    Tharos, the Announcer of Delos shouts, "It's near the end of the egghunt and I still haven't figured out how to pronounce Clean-dat-hoo."

  • AntoniusAntonius Member Posts: 4,050 @@ - Legendary Achaean
    As the script is now, no. It will only track and modify commands queued for the next balance recovery/running of that queue. You'd need to extend it - or have a separate system in place - to track a list of commands that you want to run one on each subsequent balance. My gut reaction (so without making a solid plan of what the code would actually look like) is that it wouldn't require too many modifications to allow it to also work as an actual queueing system for multiple balances, though.
  • KlendathuKlendathu Eye of the StormMember Posts: 2,448 @@ - Legendary Achaean
    edited February 15
    Made a start, slightly floundering around right now, but will get there eventually!

    antonius.queueing.doadd = function(action, ppd)
    -- add balance consuming actions to pending queue
    
    	local prio = ppd or #antonius.queueing.queues.pending+1
    	
    	if not table.contains(antonius.queueing.queues.pending,action:upper()) then
    		table.insert(antonius.queueing.queues.pending,prio,action:upper())
    	end
    
    end
    
    
    antonius.queueing.executePending = function()
    -- move actions from pending to main queue
    
    	local canadd = true
    	if #antonius.queueing.queues.pending == 0 then return false end
    
    	for k, v in pairs(antonius.queueing.queues["eqbal"]) do
    		if v.usesbal == true then
    			canadd = false
    			break
    		end
    	end
    
    	print("Can add: "..string.format("%s", tostring(canadd))) --debug echo
    		
    	if canadd == true then
    --[[
    		antonius.queueing.queue("eqbal","add",antonius.queueing.queues.pending[1],true) --probably not the right command
    		table.remove(antonius.queueing.queues.pending[1])
    ]]--
    	end
    
    end

    Tharos, the Announcer of Delos shouts, "It's near the end of the egghunt and I still haven't figured out how to pronounce Clean-dat-hoo."

Sign In to Comment.