Join queue Lua module for ET server
Author: a domestic cat (c) 2019
License: MIT
Source: https://github.com/adawolfa/et-lua-joinqueue
--]]
local clients = {}
local sv_maxclients = 64
local team_maxplayers = 0
local team_maxcovertops
local team_maxfieldops
local team_maxmortars
local team_maxpanzers
local team_maxflamers
local team_maxmg42s
local team_maxriflegrenades
local g_teamforcebalance
local shrubbot = "shrubbot.cfg"
local level_priority
local level_override
local admins = {}
local futures = {}
local delayes = {}
local announces = { axis = "", allies = "", all = "" }
local pop = true
local put = true
local sound
local introduction
local banner
local banner_delay = 10000
local banner_interval = 90000
local tick = 0
local shuffles = false
local shoutcast_announcement = false
local startupTime
local time = 0
local wasBlocked = false
local WEAPON_MORTAR = 35
local WEAPON_PANZERFAUST = 5
local WEAPON_FLAMETHROWER = 6
local WEAPON_MG42 = 31
local WEAPON_K43 = 23
local WEAPON_GARAND = 24
local CLASS_SOLDIER = 0
local CLASS_MEDIC = 1
local CLASS_ENGINEER = 2
local CLASS_FIELDOPS = 3
local CLASS_COVERTOPS = 4
function et_InitGame(levelTime, randomSeed, restart)
et.RegisterModname("joinqueue.lua " .. et.FindSelf());
startupTime = levelTime
sv_maxclients = tonumber(et.trap_Cvar_Get("sv_maxclients"))
team_maxplayers = tonumber(et.trap_Cvar_Get("team_maxplayers"))
et.trap_Cvar_Set("team_maxplayers", 0)
for i = 0, sv_maxclients - 1 do
local serialized = et.trap_Cvar_Get("jq_client" .. i)
if serialized ~= "" then
local client = {}
local valid = false
for key, value in string.gfind(serialized, "([^= ]+)=\"([^\"]*)\"") do
if key == "name" or key == "guid" then
client[key] = value
valid = true
else
client[key] = tonumber(value)
end
end
if valid then
clients[i] = client
else
clients[i] = nil
end
else
clients[i] = nil
end
end
local jq_shrubbot = et.trap_Cvar_Get("jq_shrubbot")
if jq_shrubbot ~= "" then
shrubbot = jq_shrubbot
end
local fd, len = et.trap_FS_FOpenFile(shrubbot, et.FS_READ)
if len > -1 then
local content = et.trap_FS_Read(fd, len)
for guid, level in string.gfind(content, "[Gg]uid%s*=%s*(%x+)%s*\n[Ll]evel%s*=%s*(%d+)") do
admins[string.lower(guid)] = tonumber(level)
end
end
et.trap_FS_FCloseFile(fd)
local jq_level_priority = et.trap_Cvar_Get("jq_level_priority")
local jq_level_override = et.trap_Cvar_Get("jq_level_override")
local jq_sound = et.trap_Cvar_Get("jq_sound")
local jq_introduction = et.trap_Cvar_Get("jq_introduction")
local jq_banner = et.trap_Cvar_Get("jq_banner")
local jq_banner_delay = et.trap_Cvar_Get("jq_banner_delay")
local jq_banner_interval = et.trap_Cvar_Get("jq_banner_interval")
if jq_level_priority ~= "" then
level_priority = tonumber(jq_level_priority)
end
if jq_level_override ~= "" then
level_override = tonumber(jq_level_override)
end
if jq_sound ~= "" then
sound = et.G_SoundIndex(jq_sound)
end
if jq_introduction ~= "" then
introduction = jq_introduction
end
if jq_banner ~= "" then
banner = jq_banner
end
if jq_banner_delay ~= "" then
banner_delay = tonumber(jq_banner_delay) * 1000
end
if jq_banner_interval ~= "" then
banner_interval = tonumber(jq_banner_interval) * 1000
end
team_maxfieldops = tonumber(et.trap_Cvar_Get("team_maxfieldops"))
team_maxcovertops = tonumber(et.trap_Cvar_Get("team_maxcovertops"))
team_maxmortars = tonumber(et.trap_Cvar_Get("team_maxmortars"))
team_maxpanzers = tonumber(et.trap_Cvar_Get("team_maxpanzers"))
team_maxflamers = tonumber(et.trap_Cvar_Get("team_maxflamers"))
team_maxmg42s = tonumber(et.trap_Cvar_Get("team_maxmg42s"))
team_maxriflegrenades = tonumber(et.trap_Cvar_Get("team_maxriflegrenades"))
g_teamforcebalance = tonumber(et.trap_Cvar_Get("g_teamforcebalance"))
jq_Announce(nil)
end
function et_ClientBegin(c)
jq_UpdateClient(c)
jq_PopQueue()
end
function et_ClientUserinfoChanged(c)
jq_UpdateClient(c)
jq_PopQueue()
end
function et_ClientCommand(c, command)
command = string.lower(command)
if command == "team" then
local team = string.lower(et.trap_Argv(1))
if team == "a" then
team = -1
elseif team == "r" then
team = 1
elseif team == "b" then
team = 2
elseif team == "s" then
jq_Remove(c)
return 0
else
return 0
end
if clients[c].team == 1 or clients[c].team == 2 then
if team == -1 then
return 1
end
if team == clients[c].team then
jq_Remove(c)
return 0
end
end
local class
local weapon
local weapon2
if et.trap_Argc() > 2 then
class = tonumber(et.trap_Argv(2))
end
if et.trap_Argc() > 3 then
weapon = tonumber(et.trap_Argv(3))
end
if et.trap_Argc() > 4 then
weapon2 = tonumber(et.trap_Argv(4))
end
if jq_Add(c, team, class, weapon, weapon2) then
return 1
end
elseif command == "ref" and et.trap_Argc() > 1 then
local ref = string.lower(et.trap_Argv(1))
if ref == "shuffleteamsxp_norestart" then
shuffles = true
table.insert(delayes, { func = function() shuffles = false end, frames = 40 })
elseif ref == "shuffleteamsxp" then
shuffles = true
end
elseif command == "queue" then
jq_TellQueue(c)
return 1
end
return 0
end
function et_ConsoleCommand()
if string.lower(et.trap_Argv(0)) == "ref" and et.trap_Argc() > 2 then
local command = string.lower(et.trap_Argv(1))
local force = false
local team
if command == "putaxis" then
team = 1
elseif command == "putallies" then
team = 2
elseif command == "putany" then
team = -1
elseif command == "remove" then
team = 3
elseif command == "shuffleteamsxp_norestart" then
shuffles = true
table.insert(delayes, { func = function() shuffles = false end, frames = 40 })
return 0
elseif command == "shuffleteamsxp" then
shuffles = true
return 0
elseif command == "putaxisf" then
team = 1
force = true
elseif command == "putalliesf" then
team = 2
force = true
else
return 0
end
if not put then
put = true
return 0
end
local c = jq_FindClient(et.trap_Argv(2))
if c ~= nil then
if team == 3 then
jq_Remove(c)
return 0
end
if force then
jq_Remove(c)
jq_PutTeam(c, team)
return 1
end
if clients[c].team == 1 or clients[c].team == 2 then
if team == -1 or (team ~= -1 and team == clients[c].team) then
return 1
end
end
if not jq_Add(c, team, nil, nil, nil) then
if team == -1 then
return 1
else
return 0
end
end
end
return 1
end
return 0
end
function et_ClientDisconnect(c)
jq_Remove(c)
clients[c] = nil
jq_PopQueue()
end
function et_Quit()
for i = 0, sv_maxclients - 1 do
if clients[i] ~= nil then
local serialized = ""
table.foreach(clients[i], function(key, value)
if key ~= "banner" then
serialized = serialized .. " " .. key .. "=\"" .. value .. "\""
end
end)
et.trap_Cvar_Set("jq_client" .. i, string.sub(serialized, 2, string.len(serialized)))
else
et.trap_Cvar_Set("jq_client" .. i, "")
end
end
et.trap_Cvar_Set("team_maxplayers", team_maxplayers)
end
function et_Print(message)
if shoutcast_announcement and string.sub(message, 1, 16) == "etpro shoutcast:" then
et.trap_SendServerCommand(-1, "cp \"\"\n")
end
end
function et_RunFrame(levelTime)
time = levelTime - startupTime
if table.getn(futures) > 0 then
table.foreach(futures, function(i, future) future() end)
futures = {}
end
if table.getn(delayes) > 0 then
local remains = {}
table.foreach(delayes, function(i, delay)
delay.frames = delay.frames - 1
if delay.frames == 0 then
delay.func()
else
table.insert(remains, delay)
end
end)
delayes = remains
end
if tick + 1000 <= levelTime then
tick = levelTime
for i = 0, sv_maxclients - 1 do
if clients[i] ~= nil and clients[i].team == 3 then
if clients[i].banner == nil then
clients[i].banner = tick + banner_delay
elseif clients[i].banner < tick then
jq_Banner(i)
clients[i].banner = tick + banner_interval
end
end
end
end
end
function jq_FindClient(term)
local number = tonumber(term)
if number ~= nil then
if clients[number] == nil then
return nil
end
return number
end
term = et.Q_CleanStr(term)
for i = 0, sv_maxclients - 1 do
if clients[i] ~= nil then
local name = et.Q_CleanStr(clients[i].name)
if name == term then
return i
end
end
end
return nil
end
function jq_UpdateClient(c)
if clients[c] == nil then
clients[c] = {}
end
local userinfo = et.trap_GetUserinfo(c)
clients[c].override = 0
clients[c].priority = 0
clients[c].team = tonumber(et.gentity_get(c, "sess.sessionTeam"))
clients[c].name = et.gentity_get(c, "pers.netname")
clients[c].guid = string.lower(et.Info_ValueForKey(userinfo, "cl_guid"))
if admins[clients[c].guid] ~= nil then
if level_override ~= nil and admins[clients[c].guid] >= level_override then
clients[c].override = 1
end
if level_priority ~= nil and admins[clients[c].guid] >= level_priority then
clients[c].priority = 1
end
end
local sv_privatepassword = et.trap_Cvar_Get("sv_privatepassword")
local password = et.Info_ValueForKey(userinfo, "password")
if clients[c].override == 0 and sv_privatepassword ~= "" and password == sv_privatepassword then
clients[c].override = 1
end
end
function jq_GetTeamFree()
local axis = team_maxplayers
local allies = team_maxplayers
local axisReal = 0
local alliesReal = 0
for i = 0, sv_maxclients - 1 do
if clients[i] ~= nil then
if clients[i].override == 0 then
if clients[i].team == 1 and axis > 0 then
axis = axis - 1
elseif clients[i].team == 2 and allies > 0 then
allies = allies - 1
end
end
if clients[i].team == 1 then
axisReal = axisReal + 1
elseif clients[i].team == 2 then
alliesReal = alliesReal + 1
end
end
end
return axis, allies, axisReal, alliesReal
end
function jq_GetWeakerTeam()
local axis = 0
local allies = 0
for i = 0, sv_maxclients - 1 do
if clients[i] ~= nil then
if clients[i].team == 1 then
axis = axis + 1
elseif clients[i].team == 2 then
allies = allies + 1
end
end
end
if axis > allies then
return 2
else
return 1
end
end
function jq_Add(c, team, class, weapon, weapon2)
local axis, allies, axisReal, alliesReal = jq_GetTeamFree()
if (team == 1 and axis > 0) or (team == 2 and allies > 0) or (team == clients[c].team) then
if jq_BalancingCanJoin(team, c, axisReal, alliesReal) and jq_RestrictionCanJoin(team, c, class, weapon) then
jq_Remove(c)
return false
end
end
if (team == -1 and (axis > 0 or allies > 0)) or clients[c].override == 1 then
if team == -1 then
if (axis > 0 and allies > 0) or clients[c].override == 1 then
team = jq_GetWeakerTeam()
else
if axis > 0 and jq_BalancingCanJoin(1, c, axisReal, alliesReal) then
team = 1
elseif allies > 0 and jq_BalancingCanJoin(2, c, axisReal, alliesReal) then
team = 2
end
end
end
if team ~= -1 then
jq_PutTeam(c, team, class, weapon, weapon2)
return true
end
end
local position = 0
local new = true
if clients[c].queue ~= nil then
position = clients[c].queue
new = false
else
position = jq_GetPosition(clients[c].priority) + 1
jq_Introduce(c)
end
clients[c].queue = position
clients[c].queue_team = team
clients[c].class = class
clients[c].weapon = weapon
clients[c].weapon2 = weapon2
if new then
jq_Announce(nil)
else
jq_Announce(c)
end
if new then
jq_Shoutcaster(c, true)
end
return true
end
function jq_Remove(c)
if clients[c] == nil then
return
end
local removed = false
if clients[c].queue ~= nil then
removed = true
jq_Shoutcaster(c, false)
end
clients[c].queue = nil
clients[c].queue_team = nil
clients[c].class = nil
clients[c].weapon = nil
clients[c].weapon2 = nil
clients[c].banner = nil
if removed then
table.insert(futures, function()
et.trap_SendServerCommand(c, "b 8 \"^7You have left the queue.\"\n")
end)
end
jq_Announce(nil)
end
function jq_PutTeam(c, team, class, weapon, weapon2)
jq_Remove(c)
put = false
table.insert(futures, function()
if team == -1 then
team = jq_GetWeakerTeam()
end
if team == 1 then
et.trap_SendConsoleCommand(et.EXEC_APPEND, "ref putaxis " .. c .. "\n")
else
et.trap_SendConsoleCommand(et.EXEC_APPEND, "ref putallies " .. c .. "\n")
end
if class == nil then
class = CLASS_MEDIC
end
if not jq_IsClassAllowed(team, class) then
class = CLASS_MEDIC
weapon = nil
weapon2 = nil
elseif not jq_IsWeaponAllowed(team, class, weapon) then
weapon = nil
weapon2 = nil
end
et.gentity_set(c, "sess.latchPlayerType", class)
if weapon ~= nil then
et.gentity_set(c, "sess.latchPlayerWeapon", weapon)
end
if weapon2 ~= nil then
et.gentity_set(c, "sess.latchPlayerWeapon2", weapon2)
end
end)
end
function jq_PopQueue()
if not pop or shuffles then
return
end
local axis, allies, axisReal, alliesReal = jq_GetTeamFree()
if axis == 0 and allies == 0 then
return
end
local blocked = false
table.foreach(jq_GetQueue(-1), function(i, item)
local team = item.queue_team
if time < 10000 and (blocked or tonumber(et.gentity_get(item.i, "sess.sessionTeam")) == 0) then
if wasBlocked == false then
table.insert(delayes, { func = jq_PopQueue, frames = 10 * 20 })
end
blocked = true
wasBlocked = true
return
end
if team == -1 and item.override == 0 then
if g_teamforcebalance > 0 then
if axis > 0 and jq_BalancingCanJoin(1, item.i, axisReal, alliesReal) then
team = 1
elseif allies > 0 and jq_BalancingCanJoin(2, item.i, axisReal, alliesReal) then
team = 2
end
else
if axis > allies then
team = 1
else
team = 2
end
end
end
if (team == 1 and axis == 0) or (team == 2 and allies == 0) then
return
end
if item.override == 0 then
if team == -1 or not jq_BalancingCanJoin(team, item.i, axisReal, alliesReal) then
return
end
if team == 1 then
axis = axis - 1
else
allies = allies - 1
end
end
if team == -1 then
team = jq_GetWeakerTeam()
end
if team == 1 then
axisReal = axisReal + 1
else
alliesReal = alliesReal + 1
end
pop = false
jq_PutTeam(item.i, team, item.class, item.weapon, item.weapon2)
if sound ~= nil then
table.insert(futures, function() et.G_ClientSound(item.i, sound) end)
end
end)
table.insert(futures, function()
pop = true
end)
jq_Announce(nil)
end
function jq_BalancingCanJoin(team, c, axisReal, alliesReal)
if g_teamforcebalance < 1 then
return true
end
if (clients[c].team == 3 and ((team == 1 and axisReal > alliesReal) or (team == 2 and alliesReal > axisReal))) then
return false
end
if (clients[c].team == 1 and team == 2) or (clients[c].team == 2 and team == 1) then
if team == 1 and axisReal >= alliesReal then
return false
elseif team == 2 and alliesReal >= axisReal then
return false
end
end
return true
end
function jq_RestrictionCanJoin(team, c, class, weapon)
if class ~= nil and not jq_IsClassAllowed(team, class) then
return false
elseif weapon ~= nil and not jq_IsWeaponAllowed(team, class, weapon) then
return false
end
return true
end
function jq_GetPosition(priority)
local position = 0
if priority == 0 then
position = position + 500
end
for i = 0, sv_maxclients - 1 do
if clients[i] ~= nil and clients[i].queue ~= nil and clients[i].priority == priority and clients[i].queue > position then
position = clients[i].queue
end
end
return position
end
function jq_GetQueue(team)
local items = {}
for i = 0, sv_maxclients - 1 do
if clients[i] ~= nil and clients[i].queue ~= nil and (team == -1 or clients[i].queue_team == -1 or clients[i].queue_team == team) then
local client = { i = i }
table.foreach(clients[i], function(key, value) client[key] = value end)
table.insert(items, client)
end
end
table.sort(items, function(a, b)
return a.queue < b.queue
end)
return items
end
function jq_Announce(who)
table.insert(futures, function()
local axis = ""
local allies = ""
local all = ""
local axisn = {}
local alliesn = {}
local alln = {}
table.foreach(jq_GetQueue(1), function(i, item)
if item.queue_team == -1 then
all = all .. "^7, " .. item.name
alln[item.i] = true
end
axis = axis .. "^7, " .. item.name
axisn[item.i] = true
end)
table.foreach(jq_GetQueue(2), function(i, item)
allies = allies .. "^7, " .. item.name
alliesn[item.i] = true
end)
axis = "^1AXIS ^7queue: " .. string.sub(axis, 5, string.len(axis))
allies = "^4ALLIES ^7queue: " .. string.sub(allies, 5, string.len(allies))
all = "^7Join queue: " .. string.sub(all, 5, string.len(all))
if axis ~= announces.axis or who ~= nil then
table.foreach(axisn, function(i)
if alln[i] == nil and (who == nil or who == i) then
et.trap_SendServerCommand(i, "b 8 \"" .. axis .. "\"\n")
end
end)
end
if allies ~= announces.allies or who ~= nil then
table.foreach(alliesn, function(i)
if alln[i] == nil and (who == nil or who == i) then
et.trap_SendServerCommand(i, "b 8 \"" .. allies .. "\"\n")
end
end)
end
if all ~= announces.all or who ~= nil then
table.foreach(alln, function(i)
if who == nil or who == i then
et.trap_SendServerCommand(i, "b 8 \"" .. all .. "\"\n")
end
end)
end
announces.axis = axis
announces.allies = allies
announces.all = all
end)
end
function jq_CountClasses(team, class)
local count = 0
for i = 0, sv_maxclients - 1 do
if clients[i] ~= nil and clients[i].team == team then
if tonumber(et.gentity_get(i, "sess.latchPlayerType")) == class then
count = count + 1
end
end
end
return count
end
function jq_IsClassAllowed(team, class)
if team_maxfieldops > 0 and class == CLASS_FIELDOPS and jq_CountClasses(team, CLASS_FIELDOPS) >= team_maxfieldops then
return false
elseif team_maxcovertops > 0 and class == CLASS_COVERTOPS and jq_CountClasses(team, CLASS_COVERTOPS) >= team_maxcovertops then
return false
end
return true
end
function jq_IsWeaponAllowed(team, class, weapon)
if class == CLASS_ENGINEER and team_maxriflegrenades > 0 and (weapon == WEAPON_K43 or weapon == WEAPON_GARAND) and jq_CountWeapons(team, weapon) >= team_maxriflegrenades then
return false
elseif class == CLASS_SOLDIER then
if team_maxmortars > 0 and weapon == WEAPON_MORTAR and jq_CountWeapons(team, WEAPON_MORTAR) >= team_maxmortars then
return false
elseif team_maxpanzers > 0 and weapon == WEAPON_PANZERFAUST and jq_CountWeapons(team, WEAPON_PANZERFAUST) >= team_maxpanzers then
return false
elseif team_maxflamers > 0 and weapon == WEAPON_FLAMETHROWER and jq_CountWeapons(team, WEAPON_FLAMETHROWER) >= team_maxflamers then
return false
elseif team_maxmg42s > 0 and weapon == WEAPON_MG42 and jq_CountWeapons(team, WEAPON_MG42) >= team_maxmg42s then
return false
end
end
return true
end
function jq_CountWeapons(team, weapon)
local count = 0
for i = 0, sv_maxclients - 1 do
if clients[i] ~= nil and clients[i].team == team then
if tonumber(et.gentity_get(i, "sess.latchPlayerWeapon")) == weapon then
count = count + 1
end
end
end
return count
end
function jq_Introduce(c)
if introduction ~= nil then
table.insert(futures, function()
et.trap_SendServerCommand(c, "cp \"" .. introduction .. "\"\n")
end)
end
end
function jq_Banner(c)
if banner ~= nil then
table.insert(futures, function()
et.trap_SendServerCommand(c, "chat \"" .. banner .. "\"\n")
end)
end
end
function jq_Shoutcaster(c, status)
table.insert(futures, function()
if status then
if clients[c].team == 3 then
shoutcast_announcement = true
et.trap_SendConsoleCommand(et.EXEC_APPEND, "makeshoutcaster " .. c .. "\n")
end
else
et.trap_SendConsoleCommand(et.EXEC_APPEND, "removeshoutcaster " .. c .. "\n")
end
end)
end
function jq_TellQueue(c)
table.insert(futures, function()
local list = ""
local count = 0
table.foreach(jq_GetQueue(-1), function(i, item)
count = count + 1
if item.queue_team == -1 then
list = list .. "^7, ^7(" .. count .. ")^7 "
elseif item.queue_team == 1 then
list = list .. "^7, ^1(" .. count .. ")^7 "
elseif item.queue_team == 2 then
list = list .. "^7, ^4(" .. count .. ")^7 "
end
list = list .. item.name
end)
if count == 0 then
list = "^7No players in queue."
else
list = "^7Queue: " .. string.sub(list, 5, string.len(list))
end
et.trap_SendServerCommand(c, "b 8 \"" .. list .. "\"\n")
end)
end
function et.G_ClientSound(c, sound)
local tempentity = et.G_TempEntity(et.gentity_get(c, "r.currentOrigin"), 54)
et.gentity_set(tempentity, "s.teamNum", c)
et.gentity_set(tempentity, "s.eventParm", sound)
end