local votedDifficulties = {[0] = {}, [1] = {}, [2] = {}, [3] = {}} local voters = {} local votingScreenLoop = nil local STOPTHEVOTES = false function table.removebyvalue(tbl, value) for i, v in ipairs(tbl) do if v == value then return table.remove(tbl, i) end end return nil end local function DifficultyValue(str) local coal = {["normal"] = 0, ["easy"] = 1, ["hard"] = 2, ["expert"] = 3} return coal[str] end Difficulty_Vote_Names = { {text = "Staple (Normal) ", value = "normal"}, {text = "Serene (Easy) ", value = "easy"}, {text = "Struggle (Hard) ", value = "hard"}, {text = "Sanguine (Expert) ", value = "expert"} } Difficulty_Vote_Descriptions = { "\n- No change. The default choice.", "\n- Less enemy health, slower enemy speed, no damage increases.", "\n- Increased enemy health, faster enemy speed, +5 weight to scam.\n- Enemy damage increases after every Tank/Boss Wave.", "\n- Greatly increased enemy health, much faster enemy speed, +10 weight to scam.\n- Enemy damage increases every wave.\n- Mannpowers are binding, and are erased on death." } Difficulty_Chosen_Description = { [0] = { "DIFFICULTY HAS BEEN SET TO: Staple (Normal).", " - There are no changes to the zombies' health and speed.", " - Zombie damage will double after Wave 16.", " - This is the way it is meant to be played." }, [1] = { "DIFFICULTY HAS BEEN SET TO: Serene (Easy).", " - Zombies have reduced health and move slower.", " - Zombie damage will stay the same throughout the run.", " - This is for players who are new to the mission or want a calm experience." }, [2] = { "DIFFICULTY HAS BEEN SET TO: Struggle (Hard).", " - Zombies have increased health and move faster.", " - Zombie damage will increase after every Tank/Boss Wave.", " - Damage modifier increases to 1.5x from Wave 8, then 2x from Wave 13, to 2.67x from Wave 17 onwards.", " - The negative penalties in Lucky Bonus (scam, weakening) have their weights increased by 5.", " - This is for players who seek a difficult challenge in an already difficult mission." }, [3] = { "DIFFICULTY HAS BEEN SET TO: Sanguine (Expert).", " - Zombies have greatly increased health and move much faster.", " - Zombie damage will increase after every wave, up to 3.0x damage at the final wave.", " - The negative penalties in Lucky Bonus (scam, weakening) have their weights increased by 10.", " - Mannpowers cannot be dropped, and will permanently disappear on death.", " - This is for the masochists and people who hate their lives. Abandon all hope ye who chose this." } } local function PostVoteDifficultyText(difv) local biglist = Difficulty_Chosen_Description[difv] if not biglist then return "Oops! Something went wrong! ☠︎︎" end local s = "" for i, text in ipairs(biglist) do s = (i == 1) and text or (s .. "\n" .. text) end return s end Difficulty_Menu = { timeout = 0, title = "Select a difficulty. You have 15 seconds.", itemsPerPage = 10, flags = 0, onSelect = function(player, index, value) if STOPTHEVOTES then return end local networkId = player.m_szNetworkIDString if voters[networkId] == nil then voters[networkId] = DifficultyValue(value) table.insert(votedDifficulties[DifficultyValue(value)], networkId) player:PlaySoundToSelf('ui/hint.wav') elseif voters[networkId] ~= DifficultyValue(value) then table.insert(votedDifficulties[DifficultyValue(value)], networkId) table.removebyvalue(votedDifficulties[voters[networkId]], networkId) voters[networkId] = DifficultyValue(value) player:PlaySoundToSelf('ui/hint.wav') end if isSoloMode() then -- no need to wait ents.FindByName("populator"):AcceptInput("$FinishWavespawn", "a_15_second_wait") end end } Difficulty_Menu_Spectator = { timeout = 0, title = "The players are casting their votes...", itemsPerPage = 10, flags = MENUFLAG_NO_SOUND } You_Have_Made_A_Choice_NotSolo = { timeout = 5, title = "Your team has made a choice...", flags = MENUFLAG_NO_SOUND } You_Have_Made_A_Choice = { timeout = 5, title = "You have made a choice...", flags = MENUFLAG_NO_SOUND } Choice_Made_Spectator = { timeout = 5, title = "The players have made a choice...", flags = MENUFLAG_NO_SOUND } RNG_The_Tiebreaker = { timeout = 5, title = "The RNG has intervened in your choice...", flags = MENUFLAG_NO_SOUND, } RNG_The_Tiebreaker_Spectator = { timeout = 5, title = "The RNG has intervened in the players' choice...", flags = MENUFLAG_NO_SOUND, } Default_Difficulty = { timeout = 5, title = "The mission's difficulty has defaulted...", flags = MENUFLAG_NO_SOUND, -- [1] = {text = "The difficulty has been automatically set to Staple (Normal).\n - There are no changes to the zombies' health and speed.\n- Zombie damage will double after Wave 16.\n- This is the way it is meant to be played.", disabled = true} [1] = {text = PostVoteDifficultyText(0), disabled = true} } function RemoveVote(player) networkId = player.m_szNetworkIDString table.removebyvalue(votedDifficulties[voters[networkId]], networkId) voters[networkId] = nil end function ResetVotes() votedDifficulties = {[0] = {}, [1] = {}, [2] = {}, [3] = {}} voters = {} STOPTHEVOTES = false if votingScreenLoop then timer.Stop(votingScreenLoop) end votingScreenLoop = nil for _, player in pairs(ents.GetAllPlayers()) do if player:IsRealPlayer() then player:HideMenu() end end end function DifficultyVoteStart() votingScreenLoop = timer.Create(0.22, function() local choicearray = {} for _, i in ipairs({2, 1, 3, 4}) do local ch = table.DeepCopy(Difficulty_Vote_Names[i]) -- deep copy ensures the og can be reused anytime ch.text = string.gsub(ch.text, "", string.rep("☑", #votedDifficulties[DifficultyValue(ch.value)])) .. Difficulty_Vote_Descriptions[i] table.insert(choicearray, ch) end menu = table.JoinTable(Difficulty_Menu, choicearray) for _, player in pairs(ents.GetAllPlayers()) do if player:IsRealPlayer() and player.m_iTeamNum == 2 then player:DisplayMenu(menu) elseif player:IsRealPlayer() and player.m_iTeamNum == 1 then noWriteChoices = table.DeepCopy(choicearray) for _, ch in ipairs(noWriteChoices) do ch.disabled = true end player:DisplayMenu(table.JoinTable(Difficulty_Menu_Spectator, noWriteChoices)) end end end, 0) end function DifficultyHasBeenChosen() STOPTHEVOTES = true timer.Stop(votingScreenLoop) for _, player in pairs(ents.GetAllPlayers()) do if player:IsRealPlayer() then player:HideMenu() end end timer.Create(0.22, function() local menu = (isSoloMode()) and You_Have_Made_A_Choice or You_Have_Made_A_Choice_NotSolo local highestVotes = math.max(#votedDifficulties[0], #votedDifficulties[1], #votedDifficulties[2], #votedDifficulties[3]) local highestVoted = {} local tiebroken = false local chosenDifficulty if highestVotes <= 0 then SetDifficulty(0) for _, player in pairs(ents.GetAllPlayers()) do if player:IsRealPlayer() then player:DisplayMenu(Default_Difficulty) player:PlaySoundToSelf( "Vote.Passed" ) end end return end for i = 0, 3, 1 do if highestVotes == #votedDifficulties[i] then table.insert(highestVoted, i) end end if #highestVoted > 1 then menu = RNG_The_Tiebreaker tiebroken = true end chosenDifficulty = table.Random(highestVoted) print("Chosen Difficulty: " .. chosenDifficulty) SetDifficulty(chosenDifficulty) ActivateMannpowerBind() for _, player in pairs(ents.GetAllPlayers()) do if player:IsRealPlayer() then if player.m_iTeamNum == 1 then menu = (tiebroken) and RNG_The_Tiebreaker_Spectator or Choice_Made_Spectator end -- player:DisplayMenu(table.JoinTable(menu, Difficulty_Chosen_Description[chosenDifficulty])) menu[1] = {text = PostVoteDifficultyText(chosenDifficulty), disabled = true} player:DisplayMenu(menu) player:PlaySoundToSelf( "Vote.Passed" ) end end end) end