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", 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) if vote_caller then vote_caller = players[network(#vote_caller)] else vote_caller = players[network(0)] end vote_called = network(vote_called) vote_endtime = network(vote_endtime) 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 player.vote_waittime = nil end VoteReset() end) local function VotePlayerName() return string.char((skincolors[vote_caller.skincolor].chatcolor >> 12) + 0x80) + vote_caller.name end local function VoteStop(stopper, cancelled) if cancelled then chatprint("\131*" + VotePlayerName() + "\131's vote was cancelled", true) else chatprint("\131*\130" + stopper.name + "\131 stopped " + VotePlayerName() + "\131's vote", true) end if vote_caller and #stopper == #vote_caller then vote_caller.vote_waittime = leveltime + cv_vote_stop.value * 35 end VoteReset() 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 then 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 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 VoteCall(player, cmd, arg) 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 arg then if arg:find("[^%a%d_%- /]") then CONS_Printf(player, "\133Syntax error in command.") return end cmd = cmd + " \"" + arg + "\"" end if not (string.find(cv_vote_commands.string, "^" + ccmd + " ") or string.find(cv_vote_commands.string, " " + ccmd + " ") or string.find(cv_vote_commands.string, " " + ccmd + "$") or string.find(cv_vote_commands.string, "^" + 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 vote_caller.vote_selection = true chatprint("\131*" + VotePlayerName() + "\131 calls a vote for " + vote_called) if VoteCount(true) then S_StartSound(nil, sfx_strpst) end 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) -- TODO: switch to this once the base game allows arguments to aliases --COM_BufAddText('alias vcall "vote call"') COM_AddCommand("vote", function (player, cmd, arg1, arg2) if cmd == nil then CONS_Printf(player, "No command provided. For a list of supported commands, try cmd vote help.") return end if cmd == "call" then return VoteCall(player, arg1, arg2) elseif cmd == "yes" then 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) elseif cmd == "no" then 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 VoteStop(player, true) return end CONS_Printf(player, "\133You rejected the vote.") player.vote_selection = false VoteCount(false) 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 if not vote_called then CONS_Printf(player, "\133No vote called.") return end if #player == #vote_caller or IsPlayerAdmin(player) then VoteStop(player, false) else CONS_Printf(player, "\133You are not allowed to stop that vote.") end 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)