-- https://stackoverflow.com/questions/18843610/fast-implementation-of-queues-in-lua -- implement a queue local COM_ADMIN = 1 local List = {} function List.new () return {first = 0, last = -1} end function List.pushleft (list, value) local first = list.first - 1 list.first = first list[first] = value end function List.pushright (list, value) local last = list.last + 1 list.last = last list[last] = value end function List.popleft (list) local first = list.first if first > list.last then error("list is empty") end local value = list[first] list[first] = nil -- to allow garbage collection list.first = first + 1 return value end function List.popright (list) local last = list.last if list.first > last then error("list is empty") end local value = list[last] list[last] = nil -- to allow garbage collection list.last = last - 1 return value end ------- local LinkedList = {} function LinkedList.new(value) return {value=value,next=nil} end function LinkedList.append(node,value) if node == nil then return LinkedList.new(value) end local cur = node while (cur.next != nil) do cur = cur.next end cur.next = LinkedList.new(value) return node end local ManagedList = {} function ManagedList.new() return {head=nil} end function ManagedList.pushleft(ml,value) local new_node = LinkedList.new(value) new_node.next = ml.head return {head=new_node} end function ManagedList.pushright(ml,value) if ml.head == nil then ml.head = LinkedList.new(value) else LinkedList.append(ml.head,value) end return ml end function ManagedList.insertAt(user,ml,index,value) if index < 0 then CONS_Printf(user,"index must be larger than -1. ("..index.." was given)") return ml end local prev = nil local cur_node = ml.head local cur_idx = 0 while cur_node != nil and cur_idx < index do prev = cur_node cur_node = cur_node.next cur_idx = $ + 1 end local new_node = LinkedList.new(value) new_node.next = cur_node if prev != nil then prev.next = new_node end if cur_idx != index then CONS_Printf(user,"index "..index.." is out of bounds. Item was put at end.") end if ml.head == nil or index == 0 then ml.head = new_node end return ml end function ManagedList.removeAt(user,ml,index) if index < 0 then CONS_Printf(user,"index must be larger than -1. ("..index.." was given)") return ml end local prev = nil local cur_node = ml.head local cur_idx = 0 while cur_node != nil and cur_idx < index do prev = cur_node cur_node = cur_node.next cur_idx = $ +1 end if cur_node == nil then CONS_Printf(user,"nothing to delete at index "..index) return ml end if prev != nil then -- target is beyond first node prev.next = cur_node.next else --target is first node - reassign head ml.head = cur_node.next end return ml end function ManagedList.popleft(user,ml) local result = ml.head ml = ManagedList.removeAt(user,ml,0) return result end function ManagedList.clear(user,ml) while ml.head != nil do ml = ManagedList.removeAt(user,ml,0) end return ml end function ManagedList.editAt(user,ml,index,new_value) local cur_node = ml.head local cur_idx = 0 while cur_node != nil and cur_idx < index do cur_node = cur_node.next cur_idx = $ + 1 end if cur_node == nil then CONS_Printf(user,"Index "..index.." out of range") return end cur_node.value = new_value CONS_Printf(user,"Changed command at index "..index.." to '"..new_value.."'") end function ManagedList.noop(user) CONS_Printf(user,"If this server is not CG's SubBox you are using an illegitimate copy of commandQueue. Contact cglitcher#1172") end function ManagedList.showList(user,ml) local cur_node = ml.head local is_empty = true local idx = 0 while cur_node != nil do local output = "{"..idx.."}: " .. cur_node.value CONS_Printf(user,output) cur_node = cur_node.next is_empty = false idx = $ + 1 end if is_empty then CONS_Printf(user,"") end end function ManagedList.isEmpty(ml) return ml.head == nil end --------- -- https://stackoverflow.com/questions/2282444/how-to-check-if-a-table-contains-an-element-in-lua local Set = {} function Set.new() return {} end function Set.add(set, key) set[key] = true end function Set.remove(set, key) set[key] = nil end function Set.contains(set, key) return set[key] ~= nil end --------- -- https://stackoverflow.com/questions/1426954/split-string-in-lua local function strsplit (inputstr, sep) if sep == nil then sep = "%s" end local t={} for str in string.gmatch(inputstr, "([^"..sep.."]+)") do table.insert(t, str) end return t end --------- local enabled = CV_RegisterVar({ name = "cq_commandqueue", defaultvalue = "Off", flags = CV_NETVAR, possiblevalue = CV_OnOff }) local popOnVote = CV_RegisterVar({ name = "cq_poponvote", defaultvalue = "Off", flags = CV_NETVAR, possiblevalue = CV_OnOff }) local function ensureArg(user,arg,name,idx,mustBeNumber) if arg == nil then CONS_Printf(user,"Missing argument: "..name.." (argument #"..idx..")") return false end if mustBeNumber and tonumber(arg) == nil then local output = "Invalid number for argument"..name.." (argument #"..idx..") Must be an integer" CONS_Printf(user,output) return false end return true end local function showHelp(user,command) local help_help = {} help_help[1] = "\135commandqueue by cglitcher. Queue console commands to be executed at the end of intermission one at a time. 'helpq cq_command' to show help. List of cq_commands:" help_help[2] = "\131addq popq showq addfrontq" help_help[3] = "\131 editq deleteq" help_help[4] = "\131helpq why insertq " help_help[5] = "\131cq_commandqueue cq_poponvote cq_whitelist" local help = {cq_commandqueue="\131cq_commandqueue \1331|0 \128- enable|disable command queue. (Disabled by default)" ,cq_poponvote="\131cq_poponvote \1331|0 - \128enable|disable popping the queue at the end of intermission. (Disabled by default)" ,cq_whitelist="\131cq_whitelist \133yourcommand - \128whitelist yourcommand for queuing. All commands must be whitelisted before they can be queued" ,addq= "\131addq \133yourcommand \128- Add yourcommand to the queue. The queue will execute this in FIFO fashion" ,popq= "\131popq \128- Executes the next command in the queue. This is done automatically after intermission when \129cq_poponvote\128 is enabled" ,showq= "\131showq \128- Displays all commands in the queue. Along with their indicies (zero indexed)" ,addfrontq="\131addfrontq \133yourcommand \128- Adds a command to the front of the queue. Will execute next on popq" ,editq= "\131editq \133index yourcommand\128 - changes the command at the specified index to your new command. showq to view 'indexes'" ,deleteq="\131deleteq \133index\128 - Deletes the command at the specified index. showq to view 'indexes'" ,insertq="\131insertq \133index yourcommand\128 - Inserts a command before the commdand at the specified index. showq to view 'indexes'" ,clearq="\131clearq \128 - Clears the entire queue. DANGER! Queue is not recoverable after this operation" ,helpq= "\131helpq \133cq_command \128 Shows help for the specified command queue command" ,why="Not a real command. Why does this mod exist? The intention is to give admins the ability to queue up maps to be played for their events/tournaments all at once. This eliminates the need to return to console after each race to load the next map. Ex: To start a sneaker cup:\ \"addq 'clearscores;map map06';addq 'map map07';addq 'map map08';addq 'map map09';addq 'map map10'\"\ There are more applications beyond this with a little creativity. See the message board post. \135-cg" } local text = help[command] if command == nil or text == nil then if text == nil then CONS_Printf(user,"Not a valid cq_command") end for key,value in ipairs(help_help) do CONS_Printf(user,value) end else CONS_Printf(user,text) end end local queue = ManagedList.new() local queueable = Set.new() -- From hostmod::vote.lua by Tyron. -- https://hyuu.cc/hostmod/ local function make_queueable(p, ...) if not ... then CONS_Printf(p, "Supply a command to be whitelisted for vote.") return end local target = table.concat({...}, " ") if target:match(" ") then CONS_Printf(p, "Supply just the root command, please.") return end Set.add(queueable,target) CONS_Printf(p, "Added \130"..target.."\128 to the queueable list.") end local uber_restricted_list = {"clearbans","demote","password","promote","login","ban","banip","cq_whitelist"} local uber_restricteds = Set.new() for _,cmd in ipairs(uber_restricted_list) do Set.add(uber_restricteds,cmd) end local function queue_eligible(initiator,command) local commands = strsplit(command,";") for _,cmd in pairs(commands) do cmd = cmd:gsub("'", '"') -- Let's at least theoretically let people pass in quotes... -- Find what "base" cmd is being invoked and check it against the whitelist. local spaceindex = string.find(cmd, " ", 0, true) local commandfrag = cmd if spaceindex then commandfrag = cmd:sub(0, spaceindex - 1) end if not Set.contains(queueable,commandfrag) then CONS_Printf(initiator, "Command \130"..commandfrag.."\128 isn't whitelisted for queuing.") return false end if Set.contains(uber_restricteds,commandfrag) then CONS_Printf(initiator,"Nope :) I'm not queuing that. Even if whitelisted. Admins: execute this command directly.") CONS_Printf(initiator,"Command \130"..commandfrag.."\128 should never be queued.") return false end end return true end local function showEnableMsg(user) CONS_Printf(user,"'cq_commandqueue 1' to enable (currently disabled)") end local function addToQueue(user,command) if not enabled.value then showEnableMsg(user) return end local command_good = ensureArg(user,command,"command",1,false) if not command_good then return end if not queue_eligible(user,command) then return end ManagedList.pushright(queue,command) end local function popQueue(user) if not (enabled.value) then showEnableMsg(user) return end if ManagedList.isEmpty(queue) then CONS_Printf(user,"Queue is empty. 'addq \133[your command]\128' to populate your queue") return end local command = ManagedList.popleft(user,queue).value COM_BufInsertText(user,command) end local function showQueue_old(user) local cur = queue.first local idx = 0 if not enabled.value then CONS_Printf(user,"Warning: queue is currently disabled. 'cq_commandqueue 1' to enable") end if cur > queue.last then CONS_Printf(user,"") return end while (cur <= queue.last) do local item = queue[cur] local output = "{"..idx.."}: " .. item CONS_Printf(user,output) idx = $ + 1 cur = $ + 1 end end local function showQueue(user) if not enabled.value then CONS_Printf(user,"Warning: queue is currently disabled. 'cq_commandqueue 1' to enable") end ManagedList.showList(user,queue) end local function addToQueueAt(user,index,command) if not (enabled.value) then showEnableMsg(user) return end local index_good = ensureArg(user,index,"index",1,true) local command_good = ensureArg(user,command,"command",2,false) if not index_good or not command_good then return end if not queue_eligible(user,command) then return end index = tonumber(index) ManagedList.insertAt(user,queue,index,command) end local function addToQueueAtFront(user,command) if not (enabled.value) then showEnableMsg(user) return end local command_good = ensureArg(user,command,"command",1,false) if not command_good then return end if not queue_eligible(user,command) then return end addToQueueAt(user,0,command) end local function removeFromQueueAt(user,index) if not (enabled.value) then showEnableMsg(user) return end local index_good = ensureArg(user,index,"index",1,true) if not index_good then return end index = tonumber(index) ManagedList.removeAt(user,queue,index) end local function clearQueue(user,forced) if not (enabled.value) then showEnableMsg(user) return end if forced != "-force" then CONS_Printf(user,"Are you sure? Queue cannot be recovered. 'clearq -force' to clear the queue") return end ManagedList.clear(user,queue) end local function editQueueAt(user,index,command) if not (enabled.value) then showEnableMsg(user) return end local index_good = ensureArg(user,index,"index",1,true) local command_good = ensureArg(user,command,"command",2,false) if not index_good or not command_good then return end if not queue_eligible(user,command) then return end index = tonumber(index) ManagedList.editAt(user,queue,index,command) end -- From hostmod::automate.lua by Tyron. -- https://hyuu.cc/hostmod/ local RS_THINK, RS_VOTE, RS_INT = 1, 2, 3 local roundstatus = RS_THINK addHook("ThinkFrame", function() roundstatus = RS_THINK end) addHook("IntermissionThinker", function() roundstatus = RS_INT end) addHook("VoteThinker", function() if not popOnVote.value then return end if roundstatus != RS_VOTE then COM_BufAddText(server, "popq") end roundstatus = RS_VOTE end) addHook("NetVars", function(network) queue = network(queue) queueable = network(queueable) end) COM_AddCommand("addq",addToQueue,COM_ADMIN) COM_AddCommand("popq",popQueue,COM_ADMIN) COM_AddCommand("showq",showQueue) COM_AddCommand("deleteq",removeFromQueueAt,COM_ADMIN) COM_AddCommand("insertq",addToQueueAt,COM_ADMIN) COM_AddCommand("editq",editQueueAt,COM_ADMIN) COM_AddCommand("addfrontq",addToQueueAtFront,COM_ADMIN) COM_AddCommand("clearq",clearQueue,COM_ADMIN) COM_AddCommand("helpq",showHelp) COM_AddCommand("cq_whitelist", make_queueable, COM_ADMIN) print("commandqueue has been loaded. Type helpq in console for more info.")