local vote_caller = nil local vote_called = nil local vote_endtime = 0 local vote_player_count = 0 local vote_accept_count = 0 local vote_reject_count = 0 local vote_needed_overall = 0 local cv_vote_call = CV_RegisterVar({ name = "vote_call", defaultvalue = "On", flags = CV_NETVAR, PossibleValue = CV_OnOff, }) local cv_vote_commands = CV_RegisterVar({ name = "vote_commands", defaultvalue = "exitlevel map restart nextlevel", flags = CV_NETVAR, PossibleValue = nil, }) local cv_vote_change = CV_RegisterVar({ name = "vote_change", defaultvalue = "On", flags = CV_NETVAR, PossibleValue = CV_OnOff, }) local cv_vote_majority_factor = CV_RegisterVar({ name = "vote_majority_factor", defaultvalue = "50", flags = CV_NETVAR, PossibleValue = nil, }) local cv_vote_majority_factor_of_voted = CV_RegisterVar({ name = "vote_majority_factor_of_voted", defaultvalue = "50", flags = CV_NETVAR, PossibleValue = nil, }) local cv_vote_no_stops_vote = CV_RegisterVar({ name = "vote_no_stops_vote", defaultvalue = "On", flags = CV_NETVAR, PossibleValue = CV_OnOff, }) local cv_vote_nospectators = CV_RegisterVar({ name = "vote_nospectators", defaultvalue = "Off", flags = CV_NETVAR, PossibleValue = CV_OnOff, }) local cv_vote_stop = CV_RegisterVar({ name = "vote_stop", defaultvalue = 15, flags = CV_NETVAR, PossibleValue = CV_Natural, }) local cv_vote_timeout = CV_RegisterVar({ name = "vote_timeout", defaultvalue = 24, flags = CV_NETVAR, PossibleValue = CV_Natural, }) local cv_vote_wait = CV_RegisterVar({ name = "vote_wait", defaultvalue = 120, flags = CV_NETVAR, PossibleValue = CV_Natural, }) addHook("NetVars", function (network) vote_caller = network(vote_caller) vote_called = network(vote_called) vote_endtime = network(vote_endtime) end) local function VoteContainsAny(var, text) for w in var.string:gmatch("%S+") do if w == text then return true end end return false end COM_AddCommand("addvote", function (player, cmd) CV_Set(cv_vote_commands, cv_vote_commands.string .. " " .. cmd) end, COM_ADMIN|COM_LOCAL) COM_AddCommand("delvote", function (player, cmd) local votes = string.gsub(cv_vote_commands.string, " " .. cmd .. " ", " ") votes = string.gsub(votes, "^" .. cmd .. " ", "") votes = string.gsub(votes, " " .. cmd .. "$", "") votes = string.gsub(votes, "^" .. cmd .. "$", "") CV_Set(cv_vote_commands, votes) end, COM_ADMIN|COM_LOCAL) local function VoteReset() vote_called = nil vote_caller = nil vote_endtime = 0 end addHook("MapChange", function (mapnumber) for player in players.iterate do if player.vote_waittime then -- subtract the remaining time to carry over from the previous map player.vote_waittime = player.vote_waittime - leveltime end end VoteReset() end) local function VoteName(player) if player.skincolor == 0 then -- this is the case if a dedicated server itself does a thing return "\128SERVER" end return string.char((skincolors[player.skincolor].chatcolor >> 12) + 0x80) + player.name end local function VotePlayerName() return VoteName(players[vote_caller]) end local function VoteEnd(stopper, cancelled) if cancelled then chatprint("\131*" .. VotePlayerName() .. "\131's vote was cancelled", true) else chatprint("\131*" .. VoteName(stopper) .. "\131 stopped " .. VotePlayerName() .. "\131's vote", true) end if #stopper == vote_caller then players[vote_caller].vote_waittime = leveltime + cv_vote_stop.value * 35 end VoteReset() end addHook("PlayerQuit", function (player, reason) if #player == vote_caller then VoteEnd(player, true) end end) local function VoteAccept() chatprint("\131*" .. VotePlayerName() .. "\131's vote for \133" .. vote_called .. "\131 was accepted") if isserver then COM_BufAddText(nil, vote_called) end VoteReset() S_StartSound(nil, sfx_addfil) end local function VoteReject(timeout) if timeout then chatprint("\131*" .. VotePlayerName() .. "\131's vote for \133" .. vote_called .. "\131 timed out") else chatprint("\131*" .. VotePlayerName() .. "\131's vote for \133" .. vote_called .. "\131 was rejected") end if vote_caller ~= nil then players[vote_caller].vote_waittime = leveltime + cv_vote_wait.value * 35 end VoteReset() S_StartSound(nil, sfx_notadd) end local function bound(lower, v, upper) if v < lower then v = lower end if v > upper then v = upper end return v end local function min(x, y) if x < y then return x end return y end local function VoteCount(first_count) vote_player_count = 0 vote_accept_count = 0 vote_reject_count = 0 local vote_real_player_count = 0 local vote_real_accept_count = 0 local vote_real_reject_count = 0 for player in players.iterate do if player and player.valid and not player.bot then vote_player_count = vote_player_count + 1 if not player.spectator then vote_real_player_count = vote_real_player_count + 1 end if player.vote_selection == true then vote_accept_count = vote_accept_count + 1 if not player.spectator then vote_real_accept_count = vote_real_accept_count + 1 end elseif player.vote_selection == false then vote_reject_count = vote_reject_count + 1 if not player.spectator then vote_real_reject_count = vote_real_reject_count + 1 end end end end if cv_vote_nospectators.value and vote_real_player_count then vote_player_count = vote_real_player_count vote_accept_count = vote_real_accept_count vote_reject_count = vote_real_reject_count end if vote_player_count == 0 and first_count then VoteAccept() return false end local vote_factor_overall = bound(50, cv_vote_majority_factor.value, 100) vote_needed_overall = (vote_player_count * vote_factor_overall) / 100 + 1 local vote_factor_of_voted = bound(50, cv_vote_majority_factor_of_voted.value, 100) local vote_needed_of_voted = ((vote_accept_count + vote_reject_count) * vote_factor_of_voted) / 100 + 1 if vote_accept_count >= vote_needed_overall then VoteAccept() return false end if vote_reject_count > vote_player_count - vote_needed_overall then VoteReject(false) return false end if vote_endtime <= 0 then if cv_vote_majority_factor_of_voted.value then if vote_accept_count >= vote_needed_of_voted then VoteAccept() return false end if vote_accept_count + vote_reject_count > 0 then VoteReject(false) return false end end VoteReject(true) return false end return true end local function SanitizeArg(player, arg) if arg:find("[^%a%d_%- /%.!?]") then CONS_Printf(player, "\133Syntax error in command.") return nil end return " \"" .. arg .. "\"" end local function VoteCall(player, cmd, arg1, arg2, arg3) if cmd == nil then CONS_Printf(player, "Usage: \130vote call \128\n Where is the command to request a vote upon.") CONS_Printf(player, "Examples: vote call map map01") CONS_Printf(player, " vote call exitlevel") CONS_Printf(player, "You can call a vote for or execute these commands: \130" .. cv_vote_commands.string) return end if not cv_vote_call.value then CONS_Printf(player, "\133Vote calling is not allowed.") return end if vote_called then CONS_Printf(player, "\133There is already a vote called.") return end if not player.realmo or cv_vote_nospectators.value and player.spectator then CONS_Printf(player, "\133Only players can call a vote.") return end if player.vote_waittime and leveltime < player.vote_waittime then CONS_Printf(player, "\133You have to wait \131" .. ((player.vote_waittime - leveltime) / 35) .. "\133 seconds before you can again call a vote.") return end -- we're being very strict here due to me not trusting srb2 and also Lua's string.find being regex if cmd:find("[^%a%d_%-/]") then CONS_Printf(player, "\133Syntax error in command.") return end local ccmd = cmd if arg1 then arg1 = SanitizeArg(player, arg1) if arg1 == nil then return end cmd = cmd .. arg1 end if arg2 then arg2 = SanitizeArg(player, arg2) if arg2 == nil then return end cmd = cmd .. arg2 end if arg3 then arg3 = SanitizeArg(player, arg3) if arg3 == nil then return end cmd = cmd .. arg3 end if string.len(cmd) > 203 CONS_Printf(player, "The command has been truncated to 203 characters to avoid votelocking.") cmd = string.char(string.byte(cmd, 1, 203)) end if not VoteContainsAny(cv_vote_commands, ccmd) then CONS_Printf(player, "No such vote: " .. cmd) return end vote_caller = #player vote_called = cmd vote_endtime = cv_vote_timeout.value * 35 for player in players.iterate do player.vote_selection = nil end players[vote_caller].vote_selection = true chatprint("\131*" .. VotePlayerName() .. "\131 calls a vote for \133" .. vote_called) if VoteCount(true) then S_StartSound(nil, sfx_strpst) end end local function VoteYes(player) if not vote_called then CONS_Printf(player, "\133No vote called.") return end if player.vote_selection ~= nil and not cv_vote_change.value then CONS_Printf(player, "\133You have already voted.") return end CONS_Printf(player, "\133You accepted the vote.") player.vote_selection = true VoteCount(false) end local function VoteStop(player) if not vote_called then CONS_Printf(player, "\133No vote called.") return end if #player == vote_caller or IsPlayerAdmin(player) or #player == 0 then VoteEnd(player, false) else CONS_Printf(player, "\133You are not allowed to stop that vote.") end end local function VoteNo(player) if not vote_called then CONS_Printf(player, "\133No vote called.") return end if player.vote_selection ~= nil and not cv_vote_change.value then CONS_Printf(player, "\133You have already voted.") return end if #player == vote_caller and cv_vote_no_stops_vote.value then VoteEnd(player, true) return end CONS_Printf(player, "\133You rejected the vote.") player.vote_selection = false VoteCount(false) end addHook("ThinkFrame", function () if vote_endtime > 0 then vote_endtime = vote_endtime - 1 if vote_endtime == 0 then VoteCount(false) end end end) COM_AddCommand("vcall", VoteCall) COM_AddCommand("vyes", VoteYes) COM_AddCommand("vno", VoteNo) COM_AddCommand("vstop", VoteStop) COM_AddCommand("vote", function (player, cmd, arg1, arg2, arg3, arg4) if cmd == nil then CONS_Printf(player, "No command provided. For a list of supported commands, try vote help.") return end if cmd == "call" then return VoteCall(player, arg1, arg2, arg3, arg4) elseif cmd == "yes" then return VoteYes(player) elseif cmd == "no" then return VoteNo(player) elseif cmd == "status" then if vote_called then CONS_Printf(player, "Vote for \133" .. vote_called .. "\128 called by " .. VotePlayerName() .. "\128.") else CONS_Printf(player, "\133No vote called.") end elseif cmd == "stop" then return VoteStop(player) elseif cmd == "help" then if arg1 == nil then CONS_Printf(player, "Voting commands:\n") CONS_Printf(player, " \131call\128: Create a new vote for players to decide on") CONS_Printf(player, " \131no\128: Select no in current vote") CONS_Printf(player, " \131status\128: Prints information about current vote") CONS_Printf(player, " \131stop\128: Immediately end a vote") CONS_Printf(player, " \131yes\128: Select yes in current vote") CONS_Printf(player, "\nUsage: \130vote help \128, where possible commands are listed above.") elseif arg1 == "yes" or arg1 == "no" or arg1 == "status" or arg1 == "stop" then CONS_Printf(player, "Usage: \130vote " .. arg1 .. "\128\n No arguments required.") elseif arg1 == "call" then CONS_Printf(player, "Usage: \130vote call \128\n Where is the command to request a vote upon.") CONS_Printf(player, "Examples: vote call map map01") CONS_Printf(player, " vote call exitlevel") CONS_Printf(player, "You can call a vote for or execute these commands: \130" .. cv_vote_commands.string) else CONS_Printf(player, "No documentation exists for this vote") end return else CONS_Printf(player, "Unknown vote command \"" .. cmd .. "\". For a list of supported commands, try vote help.") end end) -- constants stolen from source code local KEY_ENTER = 13 local KEY_ESCAPE = 27 local KEY_F1 = 0x80+0x3b local KEY_F2 = 0x80+0x3c local chat_on = false addHook("KeyDown", function (keyevent) if chat_on then if keyevent.num == KEY_ENTER or keyevent.num == KEY_ESCAPE then chat_on = false end return end if keyevent.num == input.gameControlToKeyNum(GC_TALKKEY) or keyevent.num == input.gameControlToKeyNum(GC_TEAMKEY) then chat_on = true end if not vote_called then return end if keyevent.num == KEY_F1 then COM_BufAddText(nil, "vote yes") return true elseif keyevent.num == KEY_F2 then COM_BufAddText(nil, "vote no") return true end end) hud.add(function (v, stplyr, cam) if not vote_endtime then return end local x = 180 local y = 145 v.drawFill(x, y, 120, 28, 28|V_SNAPTOBOTTOM|V_SNAPTORIGHT) v.drawString(x+60, y+5, "A vote has been called for", V_SNAPTOBOTTOM|V_SNAPTORIGHT, "small-center") v.drawString(x+60, y+10, vote_called, V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_YELLOWMAP, "small-center") local color = 0 if consoleplayer.vote_selection == true then color = V_GREENMAP end v.drawString(x+32, y+17, "Yes (F1) (" .. vote_accept_count .. ")", V_SNAPTOBOTTOM|V_SNAPTORIGHT|color, "small-center") color = 0 if consoleplayer.vote_selection == false then color = V_REDMAP end v.drawString(x+88, y+17, "No (F2) (" .. vote_reject_count .. ")", V_SNAPTOBOTTOM|V_SNAPTORIGHT|color, "small-center") v.drawFill(x+5, y+22, 53, 2, 0|V_SNAPTOBOTTOM|V_SNAPTORIGHT) v.drawFill(x+62, y+22, 53, 2, 0|V_SNAPTOBOTTOM|V_SNAPTORIGHT) v.drawFill(x+5, y+22, 53*vote_accept_count/vote_needed_overall, 2, 112|V_SNAPTOBOTTOM|V_SNAPTORIGHT) local offset = 53*vote_reject_count/vote_needed_overall v.drawFill(x+62+(53-offset), y+22, offset, 2, 35|V_SNAPTOBOTTOM|V_SNAPTORIGHT) end) -- -- vote utilities -- COM_AddCommand("restart", function (player) COM_BufAddText(nil, "map " .. gamemap .. " -f") end, COM_LOCAL) local cv_vote_allowed_gametypes = CV_RegisterVar({ name = "vote_allowed_gametypes", defaultvalue = "", flags = CV_NETVAR, }) COM_AddCommand("gametype", function (player, type) if type == nil then print("\133No gametype specified.") return end type = type:lower() if cv_vote_allowed_gametypes.string ~= "" and not VoteContainsAny(cv_vote_allowed_gametypes, type) then print("\131" .. type .. " \133is not a valid gametype.") return end if isserver then COM_BufAddText(nil, "map " .. gamemap .. " -gametype " .. type) end end, COM_ADMIN) COM_AddCommand("nextlevel", function (player, map) if map == nil then print("\133No map specified.") return end map = map:lower() local mapnum if map:find("^map%S%S$") then mapnum = M_MapNumber(map:sub(4, 5)) else mapnum = tonumber(map) end if mapnum == nil then print("\133Syntax error on map name.") return end G_SetCustomExitVars(mapnum) end, COM_ADMIN) COM_AddCommand("increase", function (player, var, amount) if var == nil CONS_Printf(player, "Usage: \130increase \128\n Where is the amount to increase by and is the variable to increase.") return end if amount == nil CONS_Printf(player, "\133No amount specified.") return end amount = tonumber(amount) if amount == nil CONS_Printf(player, "\133Invalid amount.") return end local cv = CV_FindVar(var) if cv == nil then CONS_Printf(player, "\133No such variable.") return end CV_Set(cv, cv.value + amount) end, COM_LOCAL) COM_AddCommand("decrease", function (player, var, amount) if var == nil CONS_Printf(player, "Usage: \130decrease \128\n Where is the amount to decrease by and is the variable to decrease.") return end if amount == nil CONS_Printf(player, "\133No amount specified.") return end amount = tonumber(amount) if amount == nil CONS_Printf(player, "\133Invalid amount.") return end local cv = CV_FindVar(var) if cv == nil then CONS_Printf(player, "\133No such variable.") return end CV_Set(cv, cv.value - amount) end, COM_LOCAL) COM_AddCommand("roll", function(player, sides, count) if sides == nil or count == nil then print("Usage: \130roll \128\n Where is how many sides the die has and is the amount of dice to roll.") return end sides = tonumber(sides) if sides == nil or sides < 1 then print("\133Invalid amount of sides.") return end count = tonumber(count) if count == nil or count < 1 then print("\133Invalid dice count.") return end local total = P_RandomKey(sides * count - count + 1) + count chatprint("\131*Rolled \133" .. count .. " " .. sides .. "\131-sided dice, landed on \133" .. total, true) end, COM_ADMIN)