--I'll add more options at a later date if people start needing this more. local VoteTitle = "Voting" local VoteSuccessTarget = "VoteSuccessRelay" local VoteFailTarget = "VoteFailRelay" local VoteTeamnum = 2 local VoteYesName = "Yes" local VoteNoName = "No" local VoteYesResult = "Yes Won!" local VoteNoResult = "No Won!" --How long the vote will last, 0 is infinite, do not do infinite local VoteTimeoutDuration = 20 --Measured as a number between 0 and 1, ceiling'd to the nearest one person --Do not use negative numbers local VotePercentageForSuccess = 0.75 --Which channel do you want to print to? I do not recommend using chat or console. --Your options are: --PRINT_TARGET_CONSOLE (0) --PRINT_TARGET_CHAT (1) --PRINT_TARGET_CENTER (2) --PRINT_TARGET_HINT (3) --PRINT_TARGET_RIGHT (4) local VotePrintTarget = 2 local VoteCallCommand = "/startVote" --If you want something special to happen whenever somebody votes either yes or no, add entries to these tables. local ProcessYesVoteFuncTable = {} local ProcessNoVoteFuncTable = {} --There is no table of functions for a vote conclusion because we target relays whenever a vote ends --You have the ability to make your own set of vote conclusion events off of that, while you do not have access --to the sourcemod menu created by this script, hence why the vote processing tables exist. --These are expected to be called externally, by other lua scripts or the popfile function VOTEINTERFACE_SetTitle(title) VoteTitle = title end function VOTEINTERFACE_SetSuccessTarget(SuccessTargetname) VoteSuccessTarget = SuccessTargetname end function VOTEINTERFACE_SetFailTarget(FailTargetname) VoteFailTarget = FailTargetname end function VOTEINTERFACE_SetTargetTeamnum(teamnum) --All incoming requests from the IO system format their args as strings, so tonumber is necessary VoteTeamnum = tonumber(teamnum) end function VOTEINTERFACE_SetYesName(yesName) VoteYesName = yesName end function VOTEINTERFACE_SetNoName(noName) VoteNoName = noName end function VOTEINTERFACE_SetYesResultString(yesString) VoteYesResult = yesString end function VOTEINTERFACE_SetNoResultString(noString) VoteNoResult = noString end function VOTEINTERFACE_SetTimeoutDuration(newDuration) --All incoming requests from the IO system format their args as strings, so tonumber is necessary VoteTimeoutDuration = tonumber(newDuration) end --Note that this specifically takes the print target index, not the constant name --There is a handy list of which constant maps to which index over by the comment for this variable function VOTEINTERFACE_SetPrintTarget(newPrintTarget) --All incoming requests from the IO system format their args as strings, so tonumber is necessary VotePrintTarget = tonumber(newPrintTarget) end function VOTEINTERFACE_SetPercentageForSuccess(newNumber) --All incoming requests from the IO system format their args as strings, so tonumber is necessary VotePercentageForSuccess = tonumber(newNumber) end function VOTEINTERFACE_SetVoteCallCommandName(newName) VoteCallCommand = newName end function VOTEINTERFACE_RegisterYesVoteHandler(handler) table.insert(ProcessYesVoteFuncTable,handler) end function VOTEINTERFACE_RegisterNoVoteHandler(handler) table.insert(ProcessNoVoteFuncTable,handler) end function VOTEINTERFACE_VoidYesVoteHandlers() ProcessYesVoteFuncTable = {} end function VOTEINTERFACE_VoidNoVoteHandlers() ProcessNoVoteFuncTable = {} end local voteCallableViaCommand = false --This is not a toggle because that sounds like a pain in the ass to manage popfile-side. function VOTEINTERFACE_MakeVoteNotCallableViaCommand() voteCallableViaCommand = false end function VOTEINTERFACE_MakeVoteCallableViaCommand() voteCallableViaCommand = true end --This is used to let the validator loop know a vote has been prematurely terminated, and that it should kill itself when that happens --If somebody asks for it, I can expose this via a global function in case your script wants to know if this script is doing its thing. local voteActive = false --Variable that stores how many players have voted in a call of StartServerVote, voided after the vote is complete local playersVoted = 0 --Number of yes votes local yesVotes = 0 --Number of no votes local noVotes = 0 --This is a function for readability local function postVoteCleanup() playersVoted = 0 yesVotes = 0 noVotes = 0 voteActive = false end local function ConcludeVote(numYes, targetYesCount, successRelay, failRelay, votingPlayers) --I don't know how you would ever go above targetYesCount but there's no reason to gamble --We support absent success and failure relays so you don't have to supply dummies if you only care --about one result. local resultString = "" if numYes >= targetYesCount then if successRelay:IsValid() then successRelay:AcceptInput("Trigger") end resultString = VoteYesResult elseif numYes < targetYesCount then if failRelay:IsValid() then failRelay:AcceptInput("Trigger") end resultString = VoteNoResult end timer.Create(0.5, function() for _, player in pairs(votingPlayers) do if player:IsValid() then player:Print(VotePrintTarget, resultString) end end end, 3) postVoteCleanup() end --prematurely terminate an active vote if a wave reload occurs AddEventCallback("mvm_reset_stats", function () postVoteCleanup() end) AddEventCallback("player_say", function(eventTable) if(voteActive == true or voteCallableViaCommand == false) then return end local player = ents.GetPlayerByUserId(eventTable.userid) local teamnum = player.m_iTeamNum if teamnum == VoteTeamnum and eventTable.text == VoteCallCommand then StartServerVote() end end) function StartServerVote() voteActive = true local successRelay = ents.FindByName(VoteSuccessTarget) local failRelay = ents.FindByName(VoteFailTarget) local menu = { timeout = VoteTimeoutDuration, title = VoteTitle, itemsPerPage = 2, flags = MENUFLAG_BUTTON_EXIT, onSelect = function(player,index) playersVoted = playersVoted + 1 if index == 1 then yesVotes = yesVotes + 1 for _, handler in pairs(ProcessYesVoteFuncTable) do handler(player, index, value) end elseif index == 2 then noVotes = noVotes + 1 for _, handler in pairs(ProcessNoVoteFuncTable) do handler(player, index, value) end end player:HideMenu() end, onCancel = nil, } menu[1] = {text = VoteYesName, value = 0, disabled = false} menu[2] = {text = VoteNoName, value = 0, disabled = false} --This should never be empty local votingPlayers = {} for _, player in pairs(ents.GetAllPlayers()) do if player.m_iTeamNum == VoteTeamnum and player:IsRealPlayer() == true then player:DisplayMenu(menu) table.insert(votingPlayers, player) end end --With default settings this will result in 5 yes votes being needed to succeed -- math.ceil((5 * 0.75)) -> math.ceil(4.5) -> 5 local requiredYesVotes = math.ceil((#votingPlayers * VotePercentageForSuccess)) local voteEndTimestamp = CurTime() + VoteTimeoutDuration local displayVoteInfo --This is a lua-ism in case you do an infinite vote, first case is a non-infinite vote that will show the --remaining time, second case is an infinite vote that will not display any kind of timer. if voteEndTimestamp ~= CurTime() then displayVoteInfo = timer.Create(0.5, function() local voteTimeLeft = voteEndTimestamp - CurTime() if voteActive == false then --Destroy timer timer.Stop(displayVoteInfo) displayerVoteInfo = nil end if voteTimeLeft <= 0 or playersVoted == #votingPlayers then ConcludeVote(yesVotes, requiredYesVotes, successRelay, failRelay, votingPlayers) end --This is a separate variable for readability and in case you do an infinite vote for some reason. local voteTimeString = "\nTime Remaining: " .. string.format("%.2f", voteTimeLeft) for _, player in pairs(votingPlayers) do player:Print(VotePrintTarget, VoteYesName .. ": " .. yesVotes .. "/" .. requiredYesVotes .. voteTimeString) end end, 0) else displayVoteInfo = timer.Create(0.5, function() if playersVoted == #votingPlayers then ConcludeVote(yesVotes, requiredYesVotes, successRelay, failRelay, votingPlayers) end if voteActive == false then --Destroy timer timer.Stop(displayVoteInfo) displayerVoteInfo = nil end for _, player in pairs(votingPlayers) do if player:IsValid() then player:Print(VotePrintTarget, VoteYesName .. ": " .. yesVotes .. "/" .. requiredYesVotes) end end end, 0) end end