Mr_3242

Quiz bot (fully interactive by others (they answer only)

May 10th, 2026 (edited)
38
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 117.17 KB | Gaming | 0 0
  1. local VERSION = "2.7.3"
  2.  
  3. local letters = {"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"}
  4. local userCooldowns = {}
  5. local currentQuestion
  6. local questionAnsweredBy
  7. local quizRunning = false
  8. local HttpService = game:GetService("HttpService")
  9. local players = game:GetService("Players")
  10. local localPlayer = players.LocalPlayer
  11. local blockedPlayers = {}
  12. local whiteListedplayers = {}
  13. local mode = "Quiz"
  14. local answeredCorrectly = {}
  15. local submittedAnswer = {}
  16. local awaitingAnswer = false
  17. local questionPoints = 1
  18. local timeSinceLastMessage = tick()
  19. local placeId = game.PlaceId
  20. local replicatedStorage = game:GetService("ReplicatedStorage")
  21. local StarterGui = game:GetService("StarterGui")
  22. local textChatService = game:GetService("TextChatService")
  23. local quizCooldown = false
  24. local answerOptionsSaid = 0 -- how many answer options have been said (0 = none, 1 = a, 2 = b, etc.). Prevents users from spamming letters before they even know what the corresponding answer option is
  25. local minMessageCooldown = 2.3 -- how much you need to wait to send another message to avoid ratelimit
  26. local whiteListEnabled = false
  27. local ContextActionService = game:GetService("ContextActionService")
  28. local UserInputService = game:GetService("UserInputService")
  29.  
  30. local settings = {
  31.     questionTimeout = 10,
  32.     userCooldown = 5,
  33.     sendLeaderBoardAfterQuestions = 0, -- only send quiz LB at end of quiz by default
  34.     automaticLeaderboards = true,
  35.     automaticCurrentQuizLeaderboard = false,
  36.     automaticServerQuizLeaderboard = true,
  37.     signStatus = true, -- Booth game: update sign text with relevant question text and timer
  38.     boothStatus = true, -- Booth game: also update your booth's text
  39.     boothGameFullMode = false, -- Booth game: use the sign/booth for all text, including answer options
  40.     boothGameSuppressNotifs = true, -- Booth game: suppress push notifications while a quiz is running
  41.     romanNumbers = false, -- Booth game: use roman numerals to minimize filtering
  42.     disableChat = false, -- Booth game: communicate exclusively through sign/booth
  43.     autoplay = false,
  44.     repeatTagged = true,
  45.     sendDetailedCategorylist = false, -- off by default since sending it this way tends to trigger the filter. Blame Roblox
  46.     removeLeavingPlayersFromLB = true,
  47. }
  48.  
  49. local numberMap = {
  50.     {1000, 'M'},
  51.     {900, 'CM'},
  52.     {500, 'D'},
  53.     {400, 'CD'},
  54.     {100, 'C'},
  55.     {90, 'XC'},
  56.     {50, 'L'},
  57.     {40, 'XL'},
  58.     {10, 'X'},
  59.     {9, 'IX'},
  60.     {5, 'V'},
  61.     {4, 'IV'},
  62.     {1, 'I'}
  63. }
  64.  
  65. function intToRoman(num)
  66.     local roman = ""
  67.     while num > 0 do
  68.         for _, v in pairs(numberMap)do
  69.             local romanChar = v[2]
  70.             local int = v[1]
  71.             while num >= int do
  72.                 roman = roman..romanChar
  73.                 num = num - int
  74.             end
  75.         end
  76.     end
  77.     return roman
  78. end
  79.  
  80. local function Chat(msg, force)
  81.     if settings.disableChat and not force then
  82.         return
  83.     end
  84.     textChatService.TextChannels.RBXGeneral:SendAsync(msg)
  85. end
  86.  
  87. local function shuffle(tbl) -- Table shuffle function by sleitnick
  88.     local rng = Random.new()
  89.     for i = #tbl, 2, -1 do
  90.         local j = rng:NextInteger(1, i)
  91.         tbl[i], tbl[j] = tbl[j], tbl[i]
  92.     end
  93.     return tbl
  94. end
  95.  
  96. local function sortAlphabetically(a, b)
  97.     return string.lower(a) < string.lower(b)
  98. end
  99.  
  100. local function roundNumber(num, numDecimalPlaces)
  101.     return tonumber(string.format("%." .. (numDecimalPlaces or 0) .. "f", num))
  102. end
  103.  
  104. -- callback value of configTable needs to be a bindable function
  105. local notifyBindable = Instance.new("BindableFunction")
  106. local function notify(title: string, text: string, configTable: { any }?, duration: number?)
  107.     configTable = configTable or {}
  108.     StarterGui:SetCore("SendNotification", {
  109.         Title = title,
  110.         Text = text,
  111.         Callback = notifyBindable,
  112.         Button1 = configTable.Button1,
  113.         Button2 = configTable.Button2,
  114.         Duration = duration
  115.     })
  116.     if configTable.Callback then
  117.         notifyBindable.OnInvoke = configTable.Callback
  118.     end
  119. end
  120.  
  121. local function setgenv(key: string, value: any)
  122.     pcall(function()
  123.         getgenv()[key] = value
  124.     end)
  125. end
  126.  
  127. if getgenv and getgenv().QUIZBOT_RUNNING then
  128.     notify("quizbot is already running", "You cannot run two instances of quizbot at once")
  129.     return
  130. end
  131. setgenv("QUIZBOT_RUNNING", true)
  132.  
  133. local function copyLatestScript() -- copies latest loadstring version of the script to clipboard
  134.     setclipboard('loadstring(game:HttpGet("https://raw.githubusercontent.com/Damian-11/quizbot/master/quizbot.luau", true))()')
  135.     notify("Script copied", "The latest version of quizbot has been copied to your clipboard")
  136. end
  137.  
  138. local DATA_FILENAME = "quizbot_data.json"
  139. type dataValueType = string | number | boolean
  140. local data: {[string]: dataValueType} = {}
  141. if isfile and isfile(DATA_FILENAME) then
  142.     data = HttpService:JSONDecode(readfile(DATA_FILENAME))
  143. end
  144.  
  145. local function writeToDataFile(key: string, value: dataValueType)
  146.     if not writefile then
  147.         notify("Can't write to file", "Your exploit does not support the writefile function. Your settings will not be saved.")
  148.         return
  149.     end
  150.     data[key] = value
  151.     writefile(DATA_FILENAME, HttpService:JSONEncode(data))
  152. end
  153. local function getDataFileValue(key: string): dataValueType?
  154.     return data[key]
  155. end
  156.  
  157. -- check if running latest version from GitHub
  158. local LATEST_VERSION_URL = "https://raw.githubusercontent.com/Damian-11/quizbot/master/version_number.luau"
  159. local success, LATEST_VERSION: string = pcall(function()
  160.     return loadstring(game:HttpGet(LATEST_VERSION_URL))() -- this returns a string, such as "2.5.7"
  161. end)
  162.  
  163. local outdated = if success and LATEST_VERSION then VERSION ~= LATEST_VERSION else false
  164. if outdated then
  165.     if not getDataFileValue("disableVersionAlert") then -- true if user has pressed "Don't show again" before
  166.         notify(
  167.             "Outdated quizbot version",
  168.             "You are running an outdated version of quizbot. Click the button below to copy the latest version of this script",
  169.             {
  170.                 Callback = copyLatestScript,
  171.                 Button1 = "Copy latest version",
  172.             },
  173.             10
  174.         )
  175.     end
  176. end
  177.  
  178. local function EscapePattern(pattern: string) -- escapes magic characters in pattern
  179.     local escapePattern = "[%(%)%.%%%+%-%*%?%[%]%^%$]"
  180.     return string.gsub(pattern, escapePattern, "%%%1")
  181. end
  182.  
  183. local antiFilteringDone: boolean
  184. local importantMessageSent: boolean -- if a important message that needs to be resent if filtered has been sent recently
  185. local messageBeforeFilter: string
  186. local answeredByAltMessage: string -- alt message specially for the correct answer text
  187. local mainQuestionSent: boolean
  188. local messageFiltered: boolean -- set to false once main question gets asked successfully without being filtered
  189. local lastMessageTime: number -- time at which the last message was picked up by the antifilering function
  190. function SendMessageWhenReady(message: string, important: boolean?, altMessage: string?) -- sends message so roblox won't rate limit it. if message is "important", script will send it again if it gets filtered/tagged first time. Altmessage is the message to send instead of original if it gets tagged
  191.     if not quizRunning or settings.disableChat then
  192.         return
  193.     end
  194.     if not settings.repeatTagged then
  195.         important = false
  196.     end
  197.     if important then
  198.         importantMessageSent = true
  199.         messageBeforeFilter = message
  200.         answeredByAltMessage = altMessage
  201.         messageFiltered = false
  202.         antiFilteringDone = false
  203.     end
  204.     if tick() - timeSinceLastMessage >= minMessageCooldown then
  205.         Chat(message)
  206.         timeSinceLastMessage = tick()
  207.     else
  208.         task.wait(minMessageCooldown - (tick() - timeSinceLastMessage))
  209.         if not quizRunning then
  210.             return
  211.         end
  212.         Chat(message)
  213.         timeSinceLastMessage = tick()
  214.     end
  215.     if important then
  216.         lastMessageTime = tick()
  217.         while (not antiFilteringDone or mainQuestionSent) and quizRunning and settings.repeatTagged do -- yields until the anti filter functions have done their job
  218.             task.wait()
  219.             if tick() - lastMessageTime > 4.5 then -- prevents the antifiltering functions yielding forever in case the message fails to send
  220.                 notify("Failed to Send Quiz Message", "Message rate limit exceeded. Avoid sending messages in the chat while a quiz is running. This could also be caused by high network latency.", nil, 10)
  221.                 messageFiltered = true -- the message not sending at all is equivalent to it being filtered
  222.                 break
  223.             end
  224.         end
  225.     end
  226.     importantMessageSent = false
  227. end
  228.  
  229. -- Booth Game integration may break at any point. I won't bother fixing it every time they update the remote events
  230. local boothGame = (placeId == 8351248417)
  231. local signRemote
  232. local changeSignTextRemote
  233. local boothFunction
  234. if placeId == 8351248417 then
  235.     -- For updating sign text
  236.     signRemote = replicatedStorage:WaitForChild("Remotes").SettingsRem
  237.     changeSignTextRemote = replicatedStorage.SharedModules.TextInputPrompt.TextInputEvent
  238.     -- For changing the booth text and getting current booth text/image
  239.     boothFunction = replicatedStorage.Remotes.Booth.BoothFunc
  240. end
  241.  
  242. local function UpdateSignText(text: string)
  243.     local sign = localPlayer.Character:FindFirstChild("Text Sign") or localPlayer.Backpack:FindFirstChild("Text Sign")
  244.     if not sign then
  245.         return
  246.     end
  247.     signRemote:FireServer("SignServer")
  248.     changeSignTextRemote:FireServer({text})
  249. end
  250.  
  251. local currentText = ""
  252. -- After quiz is done, reset booth text to what it was previously
  253. local originalBoothText = ""
  254. local boothDecalId = 0
  255. local function CaptureBoothTextAndImage()
  256.     if not boothGame or not boothFunction then
  257.         return
  258.     end
  259.     local result = boothFunction:InvokeServer("GetMyBoothTextImage")
  260.     -- Don't update originalBoothText if the text has been set by UpdateBoothgameTexts()
  261.     if result then
  262.         if result.Text and result.Text ~= currentText then
  263.             originalBoothText = result.Text
  264.         end
  265.         if result.IID then
  266.             boothDecalId = result.IID
  267.         end
  268.     end
  269. end
  270.  
  271. local function UpdateBoothText(text: string)
  272.     task.spawn(boothFunction.InvokeServer, boothFunction, "ChangeTIM", text, boothDecalId)
  273. end
  274.  
  275. -- Since the question timeout starts immediately after the
  276. -- last answer option has been said, its reading time becomes "owed".
  277. -- quizbot must ensure that the last answer option remains visible for this time.
  278. -- Currently, this is only used by booth game intergariton, and the value of
  279. -- this variable is the tick at which the debt will have been repaid
  280. local readTimeOwedTick = 0
  281.  
  282. -- Updates both the sign and booth text (if enabled)
  283. local lastUpdateTick = 0
  284. local MIN_BOOTHGAME_UPDATE_COOLDOWN = 1
  285. local function UpdateBoothgameTexts(text: string, imporant: boolean)
  286.     if boothGame and text then
  287.         if not imporant and settings.boothGameFullMode and readTimeOwedTick > tick() then
  288.             return
  289.         end
  290.         -- Enforce minimum rate limit
  291.         local timeSinceLastUpdate = tick() - lastUpdateTick
  292.         if timeSinceLastUpdate < MIN_BOOTHGAME_UPDATE_COOLDOWN then
  293.             -- If important, wait until an update can be performed instead of skipping
  294.             if imporant then
  295.                 task.wait(MIN_BOOTHGAME_UPDATE_COOLDOWN - timeSinceLastUpdate)
  296.             else
  297.                 return
  298.             end
  299.         end
  300.  
  301.         if settings.signStatus and signRemote and changeSignTextRemote then
  302.             UpdateSignText(text)
  303.         end
  304.         if settings.boothStatus and boothFunction then
  305.             if text == "" then
  306.                 UpdateBoothText(originalBoothText)
  307.             else
  308.                 UpdateBoothText(text)
  309.             end
  310.         end
  311.         currentText = text
  312.         lastUpdateTick = tick()
  313.     end
  314. end
  315.  
  316. -- Disables the annoying notificaion boxes from popping up each time sign/booth is updated
  317. local function SuppressBoothGameNotifications(suppress: boolean)
  318.     if not boothGame or not settings.boothGameSuppressNotifs then
  319.         return
  320.     end
  321.     local pushNotifScreenGui = localPlayer.PlayerGui:FindFirstChild("PushNotif")
  322.     local pushNotifSound = replicatedStorage.SharedModules.PushHandler.Presets.MainPreset.Sound
  323.     if pushNotifScreenGui then
  324.         pushNotifScreenGui.Enabled = not suppress
  325.     end
  326.     if pushNotifSound then
  327.         pushNotifSound.Volume = if suppress then 0 else 0.4 -- 0.4 is the default value
  328.     end
  329. end
  330.  
  331. local maxCharactersInMessage = 200
  332. if placeId == 5118029260 then -- GRP cuts down messages at 100 characters
  333.     maxCharactersInMessage = 100
  334. end
  335.  
  336. local endMessage = "🏁 Quiz ended"
  337.  
  338. local function CalculateReadTime(text: string): number
  339.     local timeToWait = #string.split(text, " ") * 0.4
  340.     if timeToWait < minMessageCooldown then
  341.         timeToWait = minMessageCooldown
  342.     end
  343.     return timeToWait
  344. end
  345. -------
  346.  
  347. --- Question OOP ---
  348. local question = {}
  349. question.__index = question
  350.  
  351. function question.New(quesitonText: string, options: { string }, value: number)
  352.     local newQuestion = {}
  353.     newQuestion.mainQuestion = quesitonText
  354.     newQuestion.answers = options
  355.     newQuestion.rightAnswer = letters[1]
  356.     newQuestion.rightAnswerIndex = 1
  357.     value = value or 1
  358.     newQuestion.value = value
  359.     setmetatable(newQuestion, question)
  360.     return newQuestion
  361. end
  362.  
  363. function question:Ask(): boolean
  364.     if not quizRunning then
  365.         return false
  366.     end
  367.     answerOptionsSaid = 0
  368.     local rightAnswerBeforeShuffle = self.answers[self.rightAnswerIndex]
  369.     self.answers = shuffle(self.answers)
  370.     self.rightAnswerIndex = table.find(self.answers, rightAnswerBeforeShuffle)
  371.     self.rightAnswer = letters[self.rightAnswerIndex]
  372.     if self.value > 1 then
  373.         if settings.boothGameFullMode then
  374.             UpdateBoothgameTexts("⭐ "..self.value.."x points", true)
  375.         end
  376.         SendMessageWhenReady("⭐ | "..self.value.."x points for question")
  377.         task.wait(2)
  378.     end
  379.     questionAnsweredBy = nil
  380.     UpdateBoothgameTexts(self.mainQuestion, true)
  381.     currentQuestion = self
  382.     questionPoints = self.value
  383.     mainQuestionSent = true
  384.     SendMessageWhenReady("🎙️ | "..self.mainQuestion, true)
  385.     if messageFiltered then
  386.         task.wait(3)
  387.         Chat("➡️ | Repeated filtering detected. Skipping to the next question...")
  388.         return false
  389.     end
  390.     local waitTime = CalculateReadTime(self.mainQuestion)
  391.     local timeWaited = 0
  392.     while waitTime > timeWaited do -- check if someone answered every 0.1ms while waiting to send options
  393.         if questionAnsweredBy or not quizRunning then
  394.             return true
  395.         end
  396.         task.wait(0.1)
  397.         timeWaited += 0.1
  398.     end
  399.     for i, v in ipairs(self.answers) do
  400.         if questionAnsweredBy or not quizRunning then
  401.             return true
  402.         end
  403.         if i ~= 1 then
  404.             task.wait(CalculateReadTime(self.answers[i - 1]))
  405.         end
  406.         if questionAnsweredBy or not quizRunning then
  407.             return true
  408.         end
  409.         local answerOptionText = letters[i]..")"..v -- 1 = A) 2 = B) 3 = C) etc.
  410.         if settings.boothGameFullMode then
  411.             UpdateBoothgameTexts(answerOptionText, true)
  412.         end
  413.         SendMessageWhenReady(answerOptionText, true)
  414.         answerOptionsSaid = i
  415.         readTimeOwedTick = tick() + CalculateReadTime(self.answers[i])
  416.     end
  417. end
  418.  
  419. local function splitIntoMessages(itemTable: { string }, separtor: string, clearFilter: boolean?, waitTime: number?) -- split table into multiple messages to prevent roblox cutting down the message
  420.     local tempItemList = {}
  421.     local messages = {}
  422.     local currentLength = 0
  423.     for _, item in itemTable do
  424.         if currentLength + #item + (#separtor * #tempItemList) + 6 >= maxCharactersInMessage then -- maxCharactersInMessage characters is the limit for chat messages in Roblox. For each item, we are adding a sepatator. +6 at end for " [x/x]" at the end of message
  425.             local conctatTable = table.concat(tempItemList, separtor)
  426.             table.insert(messages, conctatTable)
  427.             table.clear(tempItemList)
  428.             table.insert(tempItemList, item)
  429.             currentLength = #item
  430.         else
  431.             table.insert(tempItemList, item)
  432.             currentLength = currentLength + #item
  433.         end
  434.     end
  435.     table.insert(messages, table.concat(tempItemList, separtor))
  436.     for messageIndex, message in messages do
  437.         if quizRunning then
  438.             return
  439.         end
  440.         local messageNumberString = string.format("(%d/%d)", messageIndex, #messages) -- [current message/amount of messages]
  441.         if messageIndex == 2 or messageIndex == 3 then
  442.             if clearFilter then
  443.                 Chat("Waiting for filter...", true) -- hacky solution that prevents the second message getting filtered (works sometimes)
  444.             end
  445.             task.wait(5)
  446.         end
  447.         if quizRunning then
  448.             return
  449.         end
  450.         Chat(message.." "..messageNumberString, true)
  451.         task.wait(waitTime or CalculateReadTime(message) * 0.7) -- multiplied by 0.7 because full read time is too long for longer texts
  452.     end
  453. end
  454.  
  455. local antiAfkEnabled = false
  456. local function EnableAntiAfk() -- prevents Roblox kicking the player after 20 minutes of inactivity
  457.     -- credit to IY for antiafk code: https://github.com/EdgeIY/infiniteyield/tree/master
  458.     if antiAfkEnabled then
  459.         return
  460.     end
  461.     local GC = getconnections or get_signal_cons
  462.     if GC then
  463.         for i, v in pairs(GC(localPlayer.Idled)) do
  464.             if v["Disable"] then
  465.                 v["Disable"](v)
  466.             elseif v["Disconnect"] then
  467.                 v["Disconnect"](v)
  468.             end
  469.         end
  470.     else
  471.         local VirtualUser = cloneref(game:GetService("VirtualUser"))
  472.         localPlayer.Idled:Connect(function()
  473.             VirtualUser:CaptureController()
  474.             VirtualUser:ClickButton2(Vector2.new())
  475.         end)
  476.     end
  477.     antiAfkEnabled = true
  478.     notify("Anti-AFK enabled successfully", "You will not get kicked for being idle")
  479. end
  480.  
  481. --- Category OOP ---
  482. local categoryManager = {}
  483. local categories = {}
  484. categories.categoryList = {}
  485. categories.numberOfDifficulties = {}
  486. categoryManager.__index = categoryManager
  487.  
  488. --[[Category table reference:
  489. categories = {
  490.     categoryList = {
  491.         [categoryName] = {
  492.             easy = {{quiz1Questions}, {quiz2Questions}},
  493.             medium = {...},
  494.             ...
  495.         },
  496.         [categoryName2] = {
  497.             easy = {{quiz1Questions}, {quiz2Questions}},
  498.             medium = {...},
  499.             ...
  500.         }
  501.     }
  502.     numberOfDifficulties = {
  503.         [categoryName] = 2
  504.         [categoryName2] = 3
  505.         ...
  506.     }
  507. }
  508. ]]--
  509.  
  510. local difficultyOrder = {"", "easy", "medium", "hard"} -- the order in which difficulties should appear when sending the category list
  511. function categoryManager.New(categoryName: string, difficulty: string?)
  512.     difficulty = difficulty or "" -- if difficulty is not specified, use blank
  513.     difficulty = string.lower(difficulty)
  514.     if not categories.categoryList[categoryName] then
  515.         categories.categoryList[categoryName] = {}
  516.         categories.numberOfDifficulties[categoryName] = 0
  517.     end
  518.     if not categories.categoryList[categoryName][difficulty] then
  519.         categories.categoryList[categoryName][difficulty] = {}
  520.         categories.numberOfDifficulties[categoryName] += 1
  521.         if not table.find(difficultyOrder, difficulty) then
  522.             table.insert(difficultyOrder, difficulty)
  523.         end
  524.     end
  525.     table.insert(categories.categoryList[categoryName][difficulty], {})
  526.     local newCategory = categories.categoryList[categoryName][difficulty][#categories.categoryList[categoryName][difficulty]] -- get the new category at the end of it's difficulty table
  527.     setmetatable(newCategory, categoryManager)
  528.     return newCategory
  529. end
  530.  
  531. function categoryManager:Add(quesitonText: string, options: { string }, value: number?, customQuestion: table?)
  532.     self = customQuestion or self
  533.     local newQuestion = question.New(quesitonText, options, value)
  534.     table.insert(self, newQuestion)
  535. end
  536.  
  537. local function getDisplayNameByUsername(username)
  538.     local displayName = players:FindFirstChild(username) and players:FindFirstChild(username).DisplayName
  539.     return displayName
  540. end
  541.  
  542. --- Points OOP ---
  543. local pointManager = {}
  544. local userPoints = {}
  545. local UpdateUILeaderboard, ClearLeaderboardLabels -- Defined later
  546.  
  547. function pointManager.NewAccount(player)
  548.     userPoints[player.Name] = {}
  549.     local playerPoints = userPoints[player.Name] -- Tables are passed by reference so this will update the original table
  550.     -- Save displayname when creating an account to preserve it when the user leaves the game
  551.     -- This is needed when settings.removeLeavingPlayersFromLB is false
  552.     playerPoints.DisplayName = getDisplayNameByUsername(player.Name)
  553.     playerPoints.GlobalPoints = 0
  554.     playerPoints.CurrentQuizPoints = 0
  555.     return playerPoints
  556. end
  557.  
  558. function pointManager.AddPoints(player, points: number, type: string)
  559.     if not points or not tonumber(points) then
  560.         points = 1
  561.     end
  562.     if not type then
  563.         type = "All"
  564.     end
  565.     local playerAccount = userPoints[player.Name]
  566.     if not playerAccount then
  567.         playerAccount = pointManager.NewAccount(player)
  568.     end
  569.     if type == "All" then
  570.         playerAccount.GlobalPoints += points
  571.         if quizRunning then
  572.             playerAccount.CurrentQuizPoints += points
  573.         end
  574.     elseif playerAccount[type] then
  575.         playerAccount[type] += points
  576.     end
  577.     UpdateUILeaderboard(type)
  578. end
  579.  
  580. function pointManager.ClearQuizPoints()
  581.     for _, v in pairs(userPoints) do
  582.         v.CurrentQuizPoints = 0
  583.     end
  584.     ClearLeaderboardLabels("CurrentQuizPoints")
  585. end
  586.  
  587. function pointManager.RemoveAccount(player)
  588.     if userPoints[player.Name] then
  589.         userPoints[player.Name] = nil
  590.         UpdateUILeaderboard("All")
  591.     end
  592. end
  593.  
  594. function pointManager.ResetAllPoints()
  595.     for _, v in pairs(userPoints) do
  596.         v.GlobalPoints = 0
  597.         v.CurrentQuizPoints = 0
  598.     end
  599.     ClearLeaderboardLabels("CurrentQuizPoints")
  600.     ClearLeaderboardLabels("GlobalPoints")
  601. end
  602. -------
  603.  
  604. -- Used for low priority messages, will not wait
  605. local function SendMessageIfPossible(message)
  606.     timeSinceLastMessage = tick() - timeSinceLastMessage
  607.     if timeSinceLastMessage > 2.5 then
  608.         Chat(message)
  609.         timeSinceLastMessage = tick()
  610.     end
  611. end
  612.  
  613. local function startChatListening(message: string, player: Player)
  614.     local messageContent = string.upper(message) or ""
  615.     if not currentQuestion or questionAnsweredBy or table.find(userCooldowns, player.Name) or table.find(blockedPlayers, player.Name) or table.find(submittedAnswer, player.Name) or (whiteListEnabled and not table.find(whiteListedplayers, player.Name)) then
  616.         return
  617.     end
  618.     local matchAnswer
  619.     local minLenght = 4
  620.     if #currentQuestion.answers[currentQuestion.rightAnswerIndex] < minLenght then
  621.         minLenght = #currentQuestion.answers[currentQuestion.rightAnswerIndex] -- if minlenght is higher the the lenght of the correct answer, decrease it
  622.     end
  623.     local escapedMessage = EscapePattern(messageContent)
  624.     if #messageContent >= minLenght then
  625.         for _, v in ipairs(currentQuestion.answers) do
  626.         local escapedAnswer = EscapePattern(v)
  627.             if v:upper() == messageContent then
  628.                 matchAnswer = v
  629.                 break
  630.             elseif (string.match(v:upper(), escapedMessage) and #string.match(v:upper(), escapedMessage) >= minLenght) or string.match(messageContent, escapedAnswer:upper()) then
  631.                 if matchAnswer then -- no more than 1 match
  632.                     return
  633.                 end
  634.                 matchAnswer = v
  635.             end
  636.         end
  637.     end
  638.     local matchingLetter = nil
  639.     if not matchAnswer then -- check if single letter is specified. For example: "I think it is B"
  640.         local senderCharacter = player.Character
  641.         local character = localPlayer.Character
  642.         if not senderCharacter or not character then
  643.             return
  644.         end
  645.  
  646.         local patterns = {}
  647.         patterns[1] = "%s([A-"..letters[#currentQuestion.answers].."])%s" -- checks for letter surrounded by spaces on both sides (ex: "I think B is the right answer")
  648.         patterns[2] = "%s([B-"..letters[#currentQuestion.answers].."])$" -- checks for letter with space before it at the end of the string (ex: "I think it is B"). A excluded to prevent false matches (ex: "It is *a* dog")
  649.         patterns[3] = "^([A-"..letters[#currentQuestion.answers].."])%s" -- checks for letter with space after it at the beginning of the string (ex: "B I think")
  650.         patterns[4] = "^([A-"..letters[#currentQuestion.answers].."])$" -- checks for letter with no spaces after or before it (ex: "B")
  651.  
  652.         messageContent = string.gsub(messageContent, "[%).?!]", "") -- removes ), ., ?, and ! to recognize people saying a), b., c?, or d!
  653.         local magnitude = (character:GetPivot().Position - senderCharacter:GetPivot().Position).Magnitude -- make sure sender is not too far away to prevent false matches
  654.         if magnitude < 10 then
  655.             for i = 1, 4 do
  656.                 local match = messageContent:match(patterns[i])
  657.                 if match and table.find(letters, match) <= answerOptionsSaid then
  658.                     if matchingLetter then -- if more than one match, return
  659.                         return
  660.                     end
  661.                     matchingLetter = match
  662.                 end
  663.             end
  664.         else
  665.             matchingLetter = messageContent:match(patterns[4])
  666.             if matchingLetter then
  667.                 if table.find(letters, matchingLetter) > answerOptionsSaid then
  668.                     matchingLetter = nil
  669.                 end
  670.             end
  671.         end
  672.     end
  673.     if matchingLetter or matchAnswer then
  674.         if matchingLetter == currentQuestion.rightAnswer or matchAnswer == currentQuestion.answers[currentQuestion.rightAnswerIndex] then
  675.             if mode == "Quiz" then
  676.                 questionAnsweredBy = player
  677.                 currentQuestion = nil
  678.             else
  679.                 table.insert(submittedAnswer, player.Name)
  680.                 table.insert(answeredCorrectly, player.DisplayName)
  681.                 if #answeredCorrectly == 1 then
  682.                     pointManager.AddPoints(player, questionPoints * 1.5) -- person who answers first gets 1.5x points
  683.                 else
  684.                     pointManager.AddPoints(player, questionPoints)
  685.                 end
  686.             end
  687.         elseif mode == "Quiz" then
  688.             if awaitingAnswer then
  689.                 SendMessageIfPossible("❌ | "..player.DisplayName.." wrong answer. Try again in "..tostring(settings.userCooldown).." seconds.")
  690.             end
  691.             table.insert(userCooldowns, player.Name)
  692.             task.delay(settings.userCooldown, function()
  693.                 table.remove(userCooldowns, table.find(userCooldowns, player.Name))
  694.             end)
  695.         elseif mode == "Kahoot" then
  696.             table.insert(submittedAnswer, player.Name)
  697.         end
  698.     end
  699. end
  700.  
  701. local filtersInARow = 0
  702. local function processMessage(player: Player, message: string)
  703.     if player ~= localPlayer then
  704.         startChatListening(message, player)
  705.     else
  706.         if not (importantMessageSent or mainQuestionSent) or not quizRunning or not messageBeforeFilter or not settings.repeatTagged then
  707.             return
  708.         end
  709.         message = string.gsub(message, "&amp;", "&") -- & gets picked up as &amp; Needs to be corrected back to &
  710.         if messageBeforeFilter == message or (answeredByAltMessage and string.find(message, answeredByAltMessage)) then -- if message before and after filtering are exactly the same, the message has not been filtered
  711.             filtersInARow = 0
  712.             messageFiltered = false
  713.             mainQuestionSent = false
  714.             antiFilteringDone = true
  715.             return
  716.         elseif math.abs(#message - #messageBeforeFilter) > 8 or not string.find(message, "#") then -- if the lenght is diffrent from messageBeforeFilter the message is unrelated. Also give some space for diffrence to account for roblox weirdness with filtered lengh being diffrent from original lenght. Also checks for # to see if at least part of the message got tagged
  717.             return
  718.         elseif mainQuestionSent and messageFiltered then -- if the main question got sent once and got filtered, don't try again
  719.             mainQuestionSent = false
  720.             antiFilteringDone = true
  721.             return
  722.         end
  723.         lastMessageTime = tick() + 5 -- add 5 to account for the antifiltering wait procedures
  724.         messageFiltered = true
  725.         filtersInARow += 1
  726.         if filtersInARow == 1 then
  727.             SendMessageWhenReady("🔁 | Waiting for filter to clear and resending filtered message...")
  728.             task.wait(5) -- waiting makes the the filtering system less agressive
  729.         elseif filtersInARow == 2 then
  730.             SendMessageWhenReady("🔁 | Resending previous message because of chat filter...")
  731.             task.wait(6)
  732.         else
  733.             SendMessageWhenReady("Attempting to get around Roblox tagging. Please wait...")
  734.             task.wait(6)
  735.             filtersInARow = 0
  736.         end
  737.         if not quizRunning then
  738.             return
  739.         end
  740.         if questionAnsweredBy and answeredByAltMessage then -- proceed to say message after question asnwered only if the message is the message with the correct answer
  741.             SendMessageWhenReady(answeredByAltMessage)
  742.             antiFilteringDone = true
  743.             return
  744.         elseif questionAnsweredBy then
  745.             antiFilteringDone = true
  746.             return
  747.         end
  748.         SendMessageWhenReady(messageBeforeFilter)
  749.         antiFilteringDone = true
  750.     end
  751. end
  752.  
  753. local chatConnection = textChatService.MessageReceived:Connect(function(textChatMessage)
  754.     local player = if textChatMessage.TextSource then players:GetPlayerByUserId(textChatMessage.TextSource.UserId) else nil
  755.     if not player then
  756.         return
  757.     end
  758.     local message = textChatMessage.Text
  759.     processMessage(player, message)
  760. end)
  761.  
  762. local function awaitAnswer(targetQuestion)
  763.     if not quizRunning then
  764.         return
  765.     end
  766.     awaitingAnswer = true
  767.     local timeIsOut = false
  768.     local function Timeout()
  769.         if not quizRunning then
  770.             return
  771.         end
  772.         task.wait(settings.questionTimeout)
  773.         UpdateBoothgameTexts("⏰ "..targetQuestion.rightAnswer..")"..targetQuestion.answers[targetQuestion.rightAnswerIndex], true)
  774.         timeIsOut = true
  775.         currentQuestion = nil
  776.         questionAnsweredBy = nil
  777.         awaitingAnswer = false
  778.         SendMessageWhenReady("⏰ | Time is out! Correct answer was: "..targetQuestion.rightAnswer..")"..targetQuestion.answers[targetQuestion.rightAnswerIndex], true)
  779.     end
  780.     local function SignTime()
  781.         timeLeft = settings.questionTimeout
  782.         while timeLeft > 0 do
  783.             if questionAnsweredBy then
  784.                 return
  785.             end
  786.             local roundedTimeLeft = math.round(math.abs(timeLeft))
  787.             -- Only update on even numbers to prevent rate limits
  788.             if roundedTimeLeft % 2 == 0 then
  789.                 if settings.romanNumbers then
  790.                     task.defer(UpdateBoothgameTexts, tostring(intToRoman(roundedTimeLeft))) -- convert to roman number and then convert to string
  791.                 else
  792.                     task.defer(UpdateBoothgameTexts, tostring(roundedTimeLeft))
  793.                 end
  794.             end
  795.             timeLeft -= task.wait(1)
  796.         end
  797.     end
  798.     local timeoutCoroutine = coroutine.create(Timeout)
  799.     local signTimeCoroutine = coroutine.create(SignTime)
  800.     coroutine.resume(timeoutCoroutine)
  801.     if boothGame then
  802.         coroutine.resume(signTimeCoroutine)
  803.     end
  804.  
  805.     if mode == "Quiz" then
  806.         -- Wait for the question to be answered or the timeoutCoroutine to end
  807.         -- Simply checking the timeIsOut variable is not sufficient, as the SendMessageWhenReady function may still be running
  808.         while questionAnsweredBy == nil and (table.find({"normal", "running", "suspended"}, coroutine.status(timeoutCoroutine))) and quizRunning do
  809.             task.wait()
  810.         end
  811.         if timeIsOut or not quizRunning then
  812.             return
  813.         end
  814.         coroutine.close(timeoutCoroutine)
  815.         coroutine.close(signTimeCoroutine)
  816.         pointManager.AddPoints(questionAnsweredBy, targetQuestion.value)
  817.         task.delay(0.5, function() -- delayed to give time to the signtimecoroutine to stop changing sign text
  818.             UpdateBoothgameTexts("✔️ "..targetQuestion.rightAnswer..")"..targetQuestion.answers[targetQuestion.rightAnswerIndex], true)
  819.         end)
  820.         SendMessageWhenReady("✔️ | "..questionAnsweredBy.DisplayName.." answered correctly. Answer was: "..targetQuestion.rightAnswer..")"..targetQuestion.answers[targetQuestion.rightAnswerIndex], true, "[Player name filtered] answered correctly. Answer was: "..targetQuestion.rightAnswer..")"..targetQuestion.answers[targetQuestion.rightAnswerIndex])
  821.         questionAnsweredBy = nil
  822.         awaitingAnswer = false
  823.         table.clear(userCooldowns)
  824.     else
  825.         while table.find({"normal", "running", "suspended"}, coroutine.status(timeoutCoroutine)) and quizRunning do
  826.             task.wait(1)
  827.             questionPoints -= questionPoints / settings.questionTimeout
  828.         end
  829.         task.wait(2)
  830.         if not quizRunning then
  831.             return
  832.         end
  833.         if #answeredCorrectly > 0 then
  834.             local tempuserList = {} -- split players into multiple messages to prevent roblox cutting down the message
  835.             local currentLength = 37
  836.             local firstIteration = true
  837.             for _, user in pairs(answeredCorrectly) do
  838.                 if currentLength + #user + (2 * #tempuserList) >= maxCharactersInMessage then -- maxCharactersInMessage is the limit for chat messages in Roblox. For each user, we are adding 2 more characters (, )
  839.                     if firstIteration then
  840.                         SendMessageWhenReady("✔️ | Players who answered correctly: "..table.concat(tempuserList, ", "))
  841.                         UpdateBoothgameTexts("✔️ "..targetQuestion.rightAnswer..")"..targetQuestion.answers[targetQuestion.rightAnswerIndex], true)
  842.                         firstIteration = false
  843.                     else
  844.                         SendMessageWhenReady(table.concat(tempuserList, ", "))
  845.                     end
  846.                     task.wait(3)
  847.                     table.clear(tempuserList)
  848.                     table.insert(tempuserList, user)
  849.                     currentLength = #user
  850.                 else
  851.                     table.insert(tempuserList, user)
  852.                     currentLength = currentLength + #user
  853.                 end
  854.             end
  855.             if #tempuserList > 0 then
  856.                 if firstIteration then
  857.                     SendMessageWhenReady("✔️ | Players who answered correctly: "..table.concat(tempuserList, ", "))
  858.                     firstIteration = false
  859.                 else
  860.                     SendMessageWhenReady(table.concat(tempuserList, ", "))
  861.                 end
  862.             end
  863.         end
  864.         table.clear(answeredCorrectly)
  865.         table.clear(submittedAnswer)
  866.         awaitingAnswer = false
  867.         currentQuestion = nil
  868.     end
  869. end
  870.  
  871. --- Questions ---
  872. -- Built-in quiz definitions are wrapped in do-end block to prevent reaching the 200 local variable limit
  873. do
  874.  
  875. local countriesEasy = categoryManager.New("Guess the country", "easy")
  876. countriesEasy:Add("What country is this? 🇹🇷", {"Turkey", "Spain", "Greece", "Cyprus"})
  877. countriesEasy:Add("What country is this? 🇪🇸", {"Spain", "Portugal", "Greece", "Mexico"})
  878. countriesEasy:Add("What country is this? 🇵🇱", {"Poland", "Indonesia", "Austria", "Greenland"})
  879. countriesEasy:Add("What country is this? 🇮🇳", {"India", "Pakistan", "Sri Lanka", "Afghanistan"})
  880. countriesEasy:Add("What country is this? 🇳🇴", {"Norway", "Sweden", "Denmark", "Iceland"}, 2)
  881.  
  882. local countriesEasy2 = categoryManager.New("Guess the country", "easy")
  883. countriesEasy2:Add("What country is this? 🇫🇷", {"France", "England", "Netherlands", "Russia"})
  884. countriesEasy2:Add("What country is this? 🇬🇷", {"Greece", "Serbia", "Argentina", "Spain"})
  885. countriesEasy2:Add("What country is this? 🇦🇷", {"Argentina", "Honduras", "Estonia", "Brazil"})
  886. countriesEasy2:Add("What country is this? 🇻🇳", {"Vietnam", "China", "Japan", "Bejing"})
  887. countriesEasy2:Add("What country is this? 🇷🇸", {"Serbia", "Bosnia", "Croatia", "Slovakia"}, 2)
  888.  
  889. local countriesMedium = categoryManager.New("Guess the country", "medium")
  890. countriesMedium:Add("What country is this? 🇲🇽", {"Mexico", "Netherlands", "Iran", "Spain"})
  891. countriesMedium:Add("What country is this? 🇵🇹", {"Portugal", "Brazil", "Madrid", "Spain"})
  892. countriesMedium:Add("What country is this? 🇲🇦", {"Morocco", "Vietnam", "China", "Israel"}, 2)
  893. countriesMedium:Add("What country is this? 🇧🇪", {"Belgium", "Germany", "France", "Romania"})
  894. countriesMedium:Add("What country is this? 🇮🇩", {"Indonesia", "Poland", "Peru", "Switzerland"})
  895.  
  896. local countriesHard = categoryManager.New("Guess the country", "hard")
  897. countriesHard:Add("What country is this? 🇸🇮", {"Slovenia", "Slovakia", "Russia", "Serbia"})
  898. countriesHard:Add("What country is this? 🇪🇷", {"Eritrea", "Ecuador", "El Salvador"})
  899. countriesHard:Add("What country is this? 🇫🇮", {"Finland", "Sweden", "Falkland Islands"})
  900. countriesHard:Add("What country is this? 🇿🇲", {"Zambia", "Zimbabwe", "Zaire"}, 2)
  901. countriesHard:Add("What country is this? 🇸🇴", {"Somalia", "Solomon Islands", "Samoa"})
  902.  
  903. local science = categoryManager.New("Science", "medium")
  904. science:Add("The standard unit of measurement used for measuring force is which of the following?", {"Newton", "Mile", "Watt", "Kilogram"})
  905. science:Add("How long does it take the earth to do one full rotation of the sun?", {"365 days", "7 days", "30 days"})
  906. science:Add("Oil, natural gas and coal are examples of …", {"Fossil fuels", "Renewable resources", "Biofuels", "Geothermal resources"}, 2)
  907. science:Add("Why do our pupils constrict in bright light?", {"To let in less light", "To give our eyes more oxygen", "To change our vision to 3D"})
  908. science:Add("What is cooling lava called?", {"Igneous rocks", "Magma", "Fossils"})
  909.  
  910. local science2 = categoryManager.New("Science", "medium")
  911. science2:Add("What is faster, sound or light?", {"Light", "Sound", "They travel at the same speed", "They don't move"})
  912. science2:Add("Which of these is NOT one of Newton's laws of motion?", {"Objects at rest stay in motion", "Force equals mass times acceleration", "Every action has an equal and opposite reaction", "An object in motion stays in motion"})
  913. science2:Add("Who developed the theory of relativity?", {"Einstein", "Newton", "Galileo", "Darwin"})
  914. science2:Add("Which of these is not a state of matter?", {"Energy", "Solid", "Liquid", "Gas"})
  915. science2:Add("What is the powerhouse of the cell?", {"Mitochondria", "Nucleus", "Cytoplasm", "Nucleic membrane"}, 2)
  916.  
  917. local history = categoryManager.New("History", "medium")
  918. history:Add("Which of these countries did the Soviet Union NEVER invade?", {"Sweden", "Afghanistan", "Finland", "Poland"})
  919. history:Add("What was the main cause of the French Revolution in 1789?", {"The social and economic inequality of the Third Estate", "The invasion of Napoleon Bonaparte", "Disputes over territorial boundaries with neighboring countries", "The spread of the Black Death"})
  920. history:Add("What ancient civilization built the Machu Picchu complex?", {"Inca", "Aztec", "Maya", "Egypt"})
  921. history:Add("What do people call the era before written records?", {"Prehistory", "Medieval Times", "Renaissance", "Industrial Age"})
  922. history:Add("Which of these historical events happened first?", {"The American Revolution", "The French Revolution", "The Industrial Revolution", "The Russian Revolution"}, 2)
  923.  
  924. local history2 = categoryManager.New("History", "medium")
  925. history2:Add("The disease that killed a third of Europe's population in the 14th century is known as:", {"Plague (Black Death)", "Spanish Flu", "Smallpox", "Malaria"})
  926. history2:Add("Who discovered America in 1492?", {"Christopher Columbus", "Marco Polo", "Ferdinand Magellan", "Leif Erikson"})
  927. history2:Add("Which country gifted the Statue of Liberty to the United States?", {"France", "Germany", "United Kingdom", "Spain"})
  928. history2:Add("What was the name of the period of renewed interest in art and learning?", {"Renaissance", "Reformation", "Enlightenment", "Industrial Revolution"}, 2)
  929. history2:Add("Which famous trade route connected Europe and Asia?", {"Silk Road", "Spice Route", "Amber Road", "Trans-Saharan Route"})
  930.  
  931. local foodAndDrink = categoryManager.New("Food and Drink", "medium")
  932. foodAndDrink:Add("Which country is the largest producer of coffee in the world?", {"Brazil", "Vietnam", "Colombia", "Ethiopia"})
  933. foodAndDrink:Add("What is the name of the Italian dessert made from layers of sponge cake soaked in coffee and mascarpone cheese?", {"Tiramisu", "Maritozzo", "Cannoli", "Zabaglione"})
  934. foodAndDrink:Add("What is the national dish of England?", {"Fish and Chips", "Shepherd's Pie", "Yorkshire Pudding", "Sunday Roast"})
  935. foodAndDrink:Add("What is the name of the fermented milk drink that is popular in Central Asia & Eastern Europe?", {"Kefir", "Karin", "Kulmel", "Yogurt"})
  936. foodAndDrink:Add("Which country does feta cheese come from?", {"Greece", "Switzerland", "Spain", "France"}, 2)
  937.  
  938. local trivia = categoryManager.New("Trivia", "medium")
  939. trivia:Add("Which is NOT a Nobel Prize category?", {"Mathematics", "Physics", "Literature", "Chemistry"})
  940. trivia:Add("Which musical instrument has 47 strings and seven pedals?", {"Harp", "Piano", "Guitar", "Violin"})
  941. trivia:Add("What is the capital city of Japan?", {"Tokyo", "Beijing", "Seoul", "Bangkok"})
  942. trivia:Add("Which country is the only one to have a non-rectangular flag?", {"Nepal", "Switzerland", "Japan", "Qatar"})
  943. trivia:Add("'Bokmål' and 'Nynorsk' are the two official written forms of which language?", {"Norwegian", "Italian", "Danish", "Spanish"}, 2)
  944.  
  945. local trivia2 = categoryManager.New("Trivia", "medium")
  946. trivia2:Add("Which animal is the national emblem of Australia?", {"Kangaroo", "Koala", "Emu", "Platypus"})
  947. trivia2:Add("What does the Richter scale measure?", {"Earthquake intensity", "Wind Speed", "Temperature", "Tornado Strength"}, 2)
  948. trivia2:Add("Which currency is used in Japan?", {"Yen", "Dollar", "Euro", "Pound"})
  949. trivia2:Add("What is the hardest natural substance on Earth?", {"Diamond", "Gold", "Iron", "Platinum"})
  950. trivia2:Add("In sport, what does the term PGA refer to?", {"Professional Golfers Association", "Par Golfing Average", "Playing Golf Average", "Part-Time Golfing Amaterurs"})
  951.  
  952. local guessTheLanguage = categoryManager.New("Guess the language", "medium")
  953. guessTheLanguage:Add("Je suis désolé", {"French", "Spanish", "Italian", "Portuguese"})
  954. guessTheLanguage:Add("בוקר טוב", {"Hebrew", "Tamil", "Lao", "Mandarin"})
  955. guessTheLanguage:Add("Guten Tag", {"German", "Tagalog", "Finnish", "Dutch"})
  956. guessTheLanguage:Add("こんにちは", {"Japanese", "Chinese", "Turkish", "Arabic"}, 2)
  957. guessTheLanguage:Add("नमस्ते", {"Hindi", "Indonesian", "Cantonese", "Nahuatl"})
  958.  
  959. local capitals = categoryManager.New("Capital Cities", "easy")
  960. capitals:Add("What is the capital city of the USA?", {"Washington D.C.", "New York City", "Los Angeles", "Austin"})
  961. capitals:Add("What is the capital city of Finland?", {"Helsinki", "Stockholm", "Dublin", "Reykjavik"}, 2)
  962. capitals:Add("What is the capital city of Poland?", {"Warsaw", "Kiev", "Moscow", "Krakow"})
  963. capitals:Add("What is the capital city of Germany?", {"Berlin", "Frankfurt", "Hamburg", "Dusseldorf"})
  964. capitals:Add("What is the capital city of Canada?", {"Ottawa", "Toronto", "Vancouver", "Montreal"})
  965.  
  966. local capitalsHard = categoryManager.New("Capital Cities", "hard")
  967. capitalsHard:Add("What is the capital of Belgium?", {"Brussels", "Liege", "Amsterdam"})
  968. capitalsHard:Add("What is the capital of Somalia?", {"Mogadishu", "Garoowe", "Berbera"})
  969. capitalsHard:Add("What is the capital city of Mongolia?", {"Ulaanbaatar", "Hanoi", "Seoul"})
  970. capitalsHard:Add("What is the capital city of Australia?", {"Canberra", "Sydney", "Perth"})
  971. capitalsHard:Add("What is the capital city of New Zealand?", {"Wellington", "Auckland", "Hamilton"})
  972.  
  973. local geography = categoryManager.New("Geography", "easy")
  974. geography:Add("On which continent is the Sahara Desert located?", {"Africa", "Asia", "Europe", "South America"})
  975. geography:Add("Which river flows through London?", {"River Thames", "River Severn", "River Trent", "The Nile"})
  976. geography:Add("Which of these cities is NOT a national capital?", {"Sydney", "Oslo", "Wellington", "Bangkok"}, 2)
  977. geography:Add("Which continent has the largest land area?", {"Asia", "Africa", "Europe", "South America"})
  978. geography:Add("What is the smallest country in the world?", {"Vatican City", "Belgium", "Luxembourg", "Hungary"})
  979.  
  980. local geographyMedium = categoryManager.New("Geography", "medium")
  981. geographyMedium:Add("Which island is the largest in the world?", {"Greenland", "Madagascar", "Borneo", "New Guinea"})
  982. geographyMedium:Add("Which continent has the most countries?", {"Africa", "Europe", "Asia", "Australia"})
  983. geographyMedium:Add("Which one of the following countries is further north?", {"Scotland", "The Netherlands", "Belgium", "Poland"})
  984. geographyMedium:Add("What is the longest river in the world?", {"The Nile", "Amzon River", "Yangtze River", "Yellow River"})
  985. geographyMedium:Add("Which ocean is the deepest?", {"Pacific Ocean", "Atlantic Ocean", "Indian Ocean", "Arctic Ocean"}, 2)
  986.  
  987. local geographyHard = categoryManager.New("Geography", "hard")
  988. geographyHard:Add("Which country has the longest coastline?", {"Canada", "Chile", "Norway", "Australia"})
  989. geographyHard:Add("Which continent is the only one without a desert?", {"Europe", "Asia", "North America", "Africa"})
  990. geographyHard:Add("Which one of the following countries is not an enclave?", {"Italy", "Vatican City", "San Marino", "Lasotho"}, 2)
  991. geographyHard:Add("Which is the northernmost capital city in the world?", {"Reykjavik, Iceland", "Oslo, Norway", "Helsinki, Finland", "Moscow, Russia"})
  992. geographyHard:Add("Which city is the only one located on two continents?", {"Istanbul", "Cairo", "Moscow", "Panama City"})
  993.  
  994. local gaming = categoryManager.New("Gaming", "medium")
  995. gaming:Add("What is the best-selling video game of all time?", {"Minecraft", "FIFA 18", "Call of Duty: Modern Warfare 3", "Tetris"})
  996. gaming:Add("What was the first commercially successful video game?", {"Pong", "Donkey Kong Country", "Super Mario Bros", "Spacewar"})
  997. gaming:Add("What is the name of the main character in the Legend of Zelda series?", {"Link", "Zelda", "Ganon", "Mario"})
  998. gaming:Add("What video game did Mario, the Nintendo character, first appear in?", {"Donkey Kong", "Super Mario Bros", "Marios Cement Factory", "Mario Bros"}, 2)
  999. gaming:Add("What is the name of the latest virtual reality device developed by Valve?", {"Steam Frame", "Oculus Rift", "Meta Quest", "Valve VR"})
  1000.  
  1001. local gaming2 = categoryManager.New("Gaming", "medium")
  1002. gaming2:Add("Which company created the Mario franchise?", {"Nintendo", "Sony", "Microsoft", "Sega"})
  1003. gaming2:Add("What is the name of the game developer who created Half-Life, Portal, and Counter-Strike?", {"Valve", "Blizzard", "Bethesda", "Rockstar"})
  1004. gaming:Add("What is the name of the gaming console that was released by Nintendo in 2006 and featured motion controls?", {"Wii", "Switch", "GameCube", "DS"})
  1005. gaming2:Add("What is the name of the platform game series that features a plumber who rescues a princess from a turtle-like villain?", {"Super Mario", "Sonic the Hedgehog", "Crash Bandicoot", "Cuphead"})
  1006. gaming2:Add("How many standalone Grand Theft Auto titles have been released?", {"7", "5", "8", "10"}, 2)
  1007.  
  1008. local movies = categoryManager.New("Movies", "medium")
  1009. movies:Add("Which actor played the role of Jack Sparrow in the 'Pirates of the Caribbean' franchise?", {"Johnny Depp", "Orlando Bloom", "Keira Knightley", "Geoffrey Rush"})
  1010. movies:Add("What was the first movie in the Marvel Cinematic Universe?", {"Iron Man", "The Avengers", "Batman", "Spider-Man"}, 2)
  1011. movies:Add("Which movie is based on the novel by J.R.R. Tolkien?", {"The Lord of the Rings", "The Chronicles of Narnia", "The Hunger Games", "The Da Vinci Code"})
  1012. movies:Add("What is the name of the protagonist in The Matrix?", {"Neo", "Morpheus", "Trinity", "Cypher"})
  1013. movies:Add("In the movie 'Frozen', who is Olaf?", {"A snowman", "A ghost", "A knight", "A reindeer"})
  1014.  
  1015. local roblox = categoryManager.New("Roblox", "easy")
  1016. roblox:Add("What was the original name of Roblox?", {"DynaBlocks", "SuperBlocks", "XtraBlocks"})
  1017. roblox:Add("What is the name of Roblox's other virtual currency that has been removed since 2016?", {"Tix", "Builder Coins", "Ro-Points"})
  1018. roblox:Add("What program do you use to make games on Roblox?", {"Roblox Studio", "Roblox Player", "Roblox Create", "Roblox Creator"})
  1019. roblox:Add("Roblox's private servers were previously known as which of the following?", {"VIP servers", "Personal servers", "Exclusive servers"})
  1020. roblox:Add("Who won the RB Battles season 1 championship?", {"KreekCraft", "Tofuu", "Seedeng", "BriannaPlayz"}, 2)
  1021.  
  1022. local roblox2 = categoryManager.New("Roblox", "easy")
  1023. roblox2:Add("What is another name for the avatar shop?", {"Catalog", "Avatar Creator", "Avatar Editor"})
  1024. roblox2:Add("What programming language do you need to use to create Roblox games?", {"Luau", "JavaScript", "Python", "PHP"}, 2)
  1025. roblox2:Add("What is the name of Roblox's annual developer conference?", {"RDC", "Robloxcon", "Robloxx", "Blockfest"})
  1026. roblox2:Add("What was the former name of Roblox premium?", {"Builders Club", "Roblox Plus", "Roblox Pro", "VIP Club"})
  1027. roblox2:Add("What was the very first Roblox game to reach 1B+ visits?", {"MeepCity", "Arsenal", "Build a Boat For Treasure", "Adopt Me"}, 2)
  1028.  
  1029. local english = categoryManager.New("English", "easy")
  1030. english:Add("Which of the following is a proper noun?", {"London", "City", "River", "Mountain"})
  1031. english:Add("What is the correct way to punctuate this sentence?", {"However, I don't agree with your opinion.", "However I, don't agree with your opinion.", "However I don't, agree with your opinion.", "However; I don't agree with your opinion."})
  1032. english:Add("Which word is an adjective?", {"Beautiful", "Run", "Quickly", "Table"})
  1033. english:Add("Which is the correct spelling?", {"Necessary", "Neccessary", "Nessessary", "Necessay"})
  1034. english:Add("What is the subject in the sentence: 'The cat chased the mouse across the yard'?", {"The cat", "The mouse", "The yard", "Chased"}, 2)
  1035.  
  1036. local animals = categoryManager.New("Animals", "easy")
  1037. animals:Add("What is the largest land animal?", {"Elephant", "Giraffe", "Whale", "Rhino"})
  1038. animals:Add("What is the name of a baby kangaroo?", {"Joey", "Cub", "Pup", "Kit"})
  1039. animals:Add("Capable of exceeding 186 miles per hour, what is the fastest creature in the animal kingdom?", {"Peregrine falcon", "Cheetah", "Horse", "Lion"})
  1040. animals:Add("What is the only mammal that can fly?", {"Bat", "Penguin", "Pterodactyl", "Dragon"}, 2)
  1041. animals:Add("Which of these “fish” is actually a fish?", {"Swordfish", "Starfish", "Crayfish", "Jellyfish"})
  1042.  
  1043. local sports = categoryManager.New("Sports", "easy")
  1044. sports:Add("In which sport might you perform a 'bicycle kick'?", {"Football (Soccer)", "Basketball", "Rugby", "Tennis"})
  1045. sports:Add("Which country is famous for inventing sumo wrestling?", {"Japan", "China", "India", "Thailand"})
  1046. sports:Add("What animal is used in the traditional sport of polo?", {"Horse", "Camel", "Elephant", "Yak"})
  1047. sports:Add("What sport is also known as table tennis?", {"Ping Pong", "Badminton", "Squash", "Tennis"})
  1048. sports:Add("Which sport uses the terms 'strike' and 'spare'?", {"Bowling", "Baseball", "Cricket", "Golf"}, 2)
  1049.  
  1050. local minecraft = categoryManager.New("Minecraft", "easy")
  1051. minecraft:Add("What is the name of the green creature that explodes?", {"Creeper", "Zombie", "Skeleton", "Slime"})
  1052. minecraft:Add("Which tool is best for digging stone and bricks?", {"Pickaxe", "Shovel", "Axe", "Drill"})
  1053. minecraft:Add("What is the name of the dimension where you fight the Ender Dragon?", {"The End", "The Nether", "The Overworld", "The Void"}, 2)
  1054. minecraft:Add("What resource do you need to trade with villagers?", {"Emerald", "Apple", "Gold", "Iron"})
  1055. minecraft:Add("What block can you use to make a portal to the Nether?", {"Obsidian", "Netherrack", "Cobblestone", "Bedrock"})
  1056.  
  1057. local chess = categoryManager.New("Chess", "medium")
  1058. chess:Add("What is the name of the piece that can only move diagonally?", {"Bishop", "Knight", "Queen"})
  1059. chess:Add("What is the term for a situation where a king is under attack and cannot escape?", {"Checkmate", "Stalemate", "En passant", "Castling"})
  1060. chess:Add("What is the name of the chess strategy that involves sacrificing a piece to gain an advantage?", {"Gambit", "Fork", "Pin", "Skewer"})
  1061. chess:Add("What is the name of the special move where a king and a rook swap places?", {"Castling", "Promotion", "Capture", "Fork"})
  1062. chess:Add("Which piece is involved in 'en passant'?", {"Pawn", "Queen", "Bishop", "Knight"}, 2)
  1063.  
  1064. local WWII = categoryManager.New("WWII", "hard")
  1065. WWII:Add("Which countries formed the Axis powers in WWII?", {"Germany, Italy and Japan", "France, Britain and Russia", "China, India and Australia", "Canada, Mexico and Brazil"})
  1066. WWII:Add("Which country was attacked by Japan in 1941, prompting its entry into WWII?", {"USA", "China", "India", "Australia"})
  1067. WWII:Add("Which two countries were the first to declare war on Germany?", {"Britain and France", "Italy and Greece", "Norway and Denmark", "Poland and Russia"})
  1068. WWII:Add("What was the name of the operation that marked the Allied invasion of Normandy in 1944?", {"Operation Overlord", "Operation Barbarossa", "Operation Torch", "Operation Garden"}, 2)
  1069. WWII:Add("What was the name of the code-breaking machine developed by the British to crack German ciphers?", {"Bombe", "Turing", "Lorenz", "Enigma"})
  1070.  
  1071. local WWI = categoryManager.New("WWI", "hard")
  1072. WWI:Add("Which country made the first declaration of war in WWI?", {"Austria-Hungary", "Serbia", "Russia", "Germany"})
  1073. WWI:Add("What was the name of the British passenger ship that was sunk by a German submarine in 1915?", {"Lusitania", "Titanic", "Britannia", "Olympic"})
  1074. WWI:Add("What was the nickname given to the type of warfare that involved digging trenches and fighting from them?", {"Trench warfare", "Guerrilla warfare", "Dirt warfare", "Siege warfare"})
  1075. WWI:Add("What caused Great Britain to join World War I?", {"German troops marching through Belgium", "German bombing raids on London", "German use of illegal chemicals", "Germans sinking British civilian ships"})
  1076. WWI:Add("What was the name of the alliance between Germany, Austria-Hungary and Italy?", {"Triple Alliance", "The Axis Powers", "The Triple Entente", "The League of Nations"}, 2)
  1077.  
  1078. local luau = categoryManager.New("Luau", "hard")
  1079. luau:Add("What is the keyword for defining a function in Luau?", {"function", "def", "local", "sub"})
  1080. luau:Add("What is the syntax for creating a comment in Luau?", {"-- comment", "// comment", "# comment", "' comment"}, 2)
  1081. luau:Add("What is the data type for storing multiple values in Luau?", {"table", "array", "list", "set"})
  1082. luau:Add("How do you declare a table in Luau?", {"local table = {}", "local table = []", "local table = table.new()", "local table = ()"})
  1083. luau:Add("What is the symbol for concatenating strings in Luau?", {"..", "+", "&", "%"}, 2)
  1084.  
  1085. local astronomy = categoryManager.New("Astronomy", "medium")
  1086. astronomy:Add("What is the name of the dwarf planet that was once considered a ninth planet in our solar system?", {"Pluto", "Ceres", "Eris", "Haumea"})
  1087. astronomy:Add("What is the name of the theory that describes how the universe began with a massive expansion from a single point?", {"The Big Bang theory", "The Steady State theory", "The Inflationary theory", "The String theory"})
  1088. astronomy:Add("What is the name of the largest planet in our solar system?", {"Jupiter", "Saturn", "Earth", "Neptune"})
  1089. astronomy:Add("What is the term for a group of stars that form a recognizable pattern?", {"A constellation", "A nebula", "A cluster", "A galaxy"})
  1090. astronomy:Add("What is the name of the largest moon in our solar system?", {"Ganymede", "Titan", "Io", "Europa"}, 2)
  1091.  
  1092. local memes = categoryManager.New("Memes", "easy")
  1093. memes:Add("Which meme features a dog sitting in a burning room?", {"This is fine", "Doge", "Grumpy Cat", "Bad Luck Brian"})
  1094. memes:Add("What is the name of the frog character that is often associated with the phrase 'feels good man'?", {"Pepe", "Kermit", "Frogger", "Freddy"}, 2)
  1095. memes:Add("What is the term for a meme that looks low-quality and pixelated?", {"Deep fried", "Dank", "Cringe", "Ironic"})
  1096. memes:Add("Which animal is associated with the 'Doge' meme?", {"Shiba Inu", "Grumpy Cat", "Keyboard Cat", "Nyan Cat"})
  1097. memes:Add("What is the name of the meme featuring a man's head sticking out of a while toilet bowl?", {"Skibidi Toilet", "TF2 Guy", "Fanum Tax", "Smurf Cat"})
  1098.  
  1099. local anime = categoryManager.New("Anime", "easy")
  1100. anime:Add("What is the name of the main character in Naruto?", {"Naruto Uzumaki", "Naruto Uchiha", "Kakashi Naruto", "Itachi Uchiha"})
  1101. anime:Add("What is the name of the pirate crew that Monkey D. Luffy leads in One Piece?", {"Straw Hat Pirates", "Blackbeard Pirates", "Red Hair Pirates", "Whitebeard Pirates"})
  1102. anime:Add("What is the name of the powerful notebook that can kill anyone whose name is written in it in it?", {"Death Note", "Kira Note", "Shinigami Note", "Life Note"})
  1103. anime:Add("In Death Note, how does Light Yagami first discover the infamous notebook?", {"He sees it fall from the sky", "He receives it as a gift from a friend", "It is delivered to him in a mysterious box", "He finds it on the subway"}, 2)
  1104. anime:Add("What is the name of the main character in the Pokémon series?", {"Ash Ketchum", "Naruto Uzumaki", "Gary Oak", "Pikachu"})
  1105.  
  1106. local scienceHard = categoryManager.New("Science", "hard")
  1107. scienceHard:Add("What is the name of the largest bone in the human body?", {"Femur", "Humerus", "Tibia", "Pelvis"})
  1108. scienceHard:Add("Which of these particles is its own antiparticle?", {"Photon", "Proton", "Electron", "Neutron"})
  1109. scienceHard:Add("What is the name of the phenomenon in which light is scattered by particles in a medium that are not much larger than the wavelength of the light?", {"Rayleigh scattering", "Diffraction", "Refraction", "Dispersion"}, 2)
  1110. scienceHard:Add("What is the name of the branch of mathematics that deals with the properties and relationships of abstract entities such as numbers, symbols, sets, and functions?", {"Algebra", "Geometry", "Calculus", "Logic"})
  1111. scienceHard:Add("What is the name of the unit of electric potential difference, electric potential energy per unit charge?", {"Volt", "Ampere", "Ohm", "Watt"})
  1112.  
  1113. local mathCategory = categoryManager.New("Math", "easy")
  1114. mathCategory:Add("What is the value of PI (rounded to two decimal places)?", {"3.14", "3.15", "3.16", "3.17"})
  1115. mathCategory:Add("The property that states that a + b = b + a has what name?", {"Commutative property", "Associative property", "Distributive property", "Identity property"}, 2)
  1116. mathCategory:Add("What is the formula for the area of a circle?", {"pi * r^2", "2 * pi * r", "pi * d", "pi * r"})
  1117. mathCategory:Add("What is the name of the branch of mathematics that studies shapes and angles?", {"Geometry", "Algebra", "Calculus", "Arithmetic"})
  1118. mathCategory:Add("What is the value of x in the equation 2x + 5 = 13?", {"4", "3", "5", "6"})
  1119.  
  1120. local mathHard = categoryManager.New("Math", "hard")
  1121. mathHard:Add("What is the name of the theorem that states that a² + b² = c² for a right triangle?", {"Pythagorean theorem", "Fermat's last theorem", "Binomial theorem", "Euclid's theorem"})
  1122. mathHard:Add("What is the derivative of e^x?", {"e^x", "x*e^(x-1)", "ln(x)", "1/e^x"})
  1123. mathHard:Add("What is the name of the constant that is approximately equal to 2.71828?", {"Euler's number", "The golden ratio", "PI", "Planck's constant"})
  1124. mathHard:Add("What is the name of the sequence that starts with 1, 1, 2, 3, 5, 8, ...?", {"Fibonacci Sequence", "Arithmetic Sequence", "Geometric Sequence", "Harmonic Sequence"}, 2)
  1125. mathHard:Add("What is the name of the branch of mathematics that deals with patterns and sequences?", {"Combinatorics", "Algebra", "Calculus", "Geometry"})
  1126.  
  1127. local coldWar = categoryManager.New("Cold War", "hard")
  1128. coldWar:Add("In 1946 Winston Churchill popularized what term used to describe Soviet relations with Western powers?", {"Iron curtain", "Mutually assured destruction", "Quagmire", "Danger to society"})
  1129. coldWar:Add("Frequently cited as the counterpart to the CIA, what was the name of the Soviet intelligence agency?", {"KGB", "ICBM", "SALT", "DMZ"})
  1130. coldWar:Add("Devised in 1959, the DEFCON system has five stages of military readiness. Which DEFCON rating is used when a nuclear attack is imminent or already underway?", {"DEFCON 1", "DEFCON 3", "DEFCON 5"})
  1131. coldWar:Add("Although never fully leaving the organization, in 1966 what country withdrew its military from NATO and expelled NATO headquarters from its borders?", {"France", "United States", "Poland", "West Germany"})
  1132. coldWar:Add("Often seen as the Soviet version of the United States’ Vietnam quagmire, the U.S.S.R.’s 10-year-long invasion of what country began in 1979?", {"Afghanistan", "Poland", "Czechoslovakia", "Ukraine"}, 2)
  1133.  
  1134. local chemistry = categoryManager.New("Chemistry", "hard")
  1135. chemistry:Add("What is the chemical formula of water?", {"H2O", "CO2", "O2", "2HO"})
  1136. chemistry:Add("What is the name of the process that converts a solid into a gas without passing through a liquid state?", {"Sublimation", "Evaporation", "Condensation", "Deposition"})
  1137. chemistry:Add("What is the name of the element with the symbol K?", {"Potassium", "Calcium", "Krypton", "Kalium"})
  1138. chemistry:Add("What is the name of the process that separates a mixture of liquids based on their boiling points?", {"Distillation", "Filtration", "Crystallization", "Chromatography"})
  1139. chemistry:Add("What is the name of the organic compound that has the general formula CnH2n+2?", {"Alkane", "Alkene", "Alkyne", "Ammonia"}, 2)
  1140.  
  1141. local biology = categoryManager.New("Biology", "medium")
  1142. biology:Add("What is the name of the process by which plants make their own food?", {"Photosynthesis", "Respiration", "Transpiration", "Fermentation"})
  1143. biology:Add("What is the smallest unit of life?", {"Cell", "Atom", "Molecule", "Organ"})
  1144. biology:Add("What is the main function of red blood cells?", {"Oxygen transport", "Fighting infections", "Blood clotting", "Producing antibodies"})
  1145. biology:Add("What are the main building blocks of proteins?", {"Amino acids", "Fatty acids", "Nucleic acids", "Glucose"}, 2)
  1146. biology:Add("What is the name of the molecule that carries genetic information in most living organisms?", {"DNA", "Cell", "ATP", "ADP"})
  1147.  
  1148. local sayings = categoryManager.New("Sayings and Idioms", "easy")
  1149. sayings:Add("Which idiom means to reveal a secret?", {"Let the cat out of the bag", "Paint the town red", "Beat around the bush", "Bite the bullet"})
  1150. sayings:Add("What does 'a piece of cake' refer to?", {"Something very easy", "A dessert", "A difficult task", "A small portion"})
  1151. sayings:Add("Which idiom means to be in trouble?", {"In hot water", "On cloud nine", "Under the weather", "Out of the blue"})
  1152. sayings:Add("What does 'hold your horses' mean?", {"Be patient", "Ride horses", "Work hard", "Go faster"})
  1153. sayings:Add("What does 'break a leg' mean?", {"Good luck", "Actually break a leg", "Run away", "Take a break"}, 2)
  1154.  
  1155. local internetSlang = categoryManager.New("Internet Slang", "easy")
  1156. internetSlang:Add("What does 'LOL' stand for?", {"Laugh Out Loud", "Lots Of Love", "Living On Land", "Look Out Left"})
  1157. internetSlang:Add("What does 'FOMO' stand for?", {"Fear Of Missing Out", "Friends Of My Office", "Fond Of Moving On", "Full Of Many Options"})
  1158. internetSlang:Add("What does 'IMO' stand for?", {"In My Opinion", "Internet Mail Order", "It's Monday Obviously", "I Mean Okay"})
  1159. internetSlang:Add("What is the meaning of 'SMH'?", {"Shaking My Head", "So Much Hate", "Send More Help", "Smashing My Head"})
  1160. internetSlang:Add("What does 'IIRC' stand for?", {"If I Recall Correctly (If I Remember Correctly)", "It Is Really Cool (Is It Really Cool)", "I'm Incredibly Rich, Child", "Interesting Information Requires Consideration"}, 2)
  1161.  
  1162. local internetSlang2 = categoryManager.New("Internet Slang", "medium")
  1163. internetSlang2:Add("What does 'FTFY' mean?", {"Fixed That For You", "For The Following Year", "Forget That, Find Yourself", "Faster Than Fifty Yaks"})
  1164. internetSlang2:Add("What is the meaning of 'AMA'?", {"Ask Me Anything", "Always Making Assumptions", "Another Missed Appointment", "Awesome Meme Alert"})
  1165. internetSlang2:Add("What does 'YOLO' stand for?", {"You Only Live Once", "Your Own Life Obligations", "Yesterday's Old Leftover Onions", "Yelling Out Loud Often"})
  1166. internetSlang2:Add("What does 'OMW' stand for?", {"On My Way", "Oh My Word", "Only Men Welcome", "Official Meme Website"})
  1167. internetSlang2:Add("What is the meaning of 'ITT'?", {"In This Thread", "I'll Tell Them", "I Think That", "I Talked To"}, 2)
  1168.  
  1169. local guessTheMovie = categoryManager.New("Guess the Movie", "easy")
  1170. guessTheMovie:Add("Which movie features a young wizard attending the Hogwarts School?", {"Harry Potter and the Philosopher's Stone", "The Lord of the Rings", "The Chronicles of Narnia", "The Wizard of Oz"})
  1171. guessTheMovie:Add("What movie tells the story of a clownfish searching for his son across the ocean?", {"Finding Nemo", "Shark Tale", "The Little Mermaid", "Free Billy"})
  1172. guessTheMovie:Add("Which sci-fi film features blue-skinned aliens called the Na'vi?", {"Avatar", "Star Wars", "Alien", "District 9"})
  1173. guessTheMovie:Add("In what movie does Tom Hanks play a man stranded on an island with only a volleyball for company?", {"Cast Away", "The Terminal", "Forrest Gump", "Saving Private Ryan"}, 2)
  1174. guessTheMovie:Add("What movie tells the story of a group of toys that come to life when humans aren't around?", {"Toy Story", "The Lego Movie", "Small Soldiers", "Wreck-It Ralph"})
  1175.  
  1176. local guessTheBook = categoryManager.New("Guess the Book", "medium")
  1177. guessTheBook:Add("In which book does a young girl named Alice fall down a rabbit hole into a fantastical world?", {"Alice in Wonderland", "The Wonderful Wizard of Oz", "Peter Pan", "The Secret Garden"})
  1178. guessTheBook:Add("Which book tells the story of a character named Bilbo Baggins?", {"The Hobbit", "The Lord of the Rings", "The Silmarillion", "Eragon"})
  1179. guessTheBook:Add("Which book features a dystopian society where books are burned?", {"Fahrenheit 451", "Brave New World", "Lord of the Flies", "Slaughterhouse-Five"}, 2)
  1180. guessTheBook:Add("Which book tells the story of a boy named Charlie who wins a golden ticket?", {"Charlie and the Chocolate Factory", "Charlie and the Giant Peach", "Wonka's Chocolate Factory", "The BFG"})
  1181. guessTheBook:Add("Which book tells the story of a boy who never grows up and lives in Neverland?", {"Peter Pan", "The Wonderful Wizard of Oz", "Alice in Wonderland", "The Chronicles of Narnia"})
  1182.  
  1183. local music = categoryManager.New("Music", "medium")
  1184. music:Add("Which band released the album 'The Dark Side of the Moon'?", {"Pink Floyd", "The Beatles", "Led Zeppelin", "The Rolling Stones"})
  1185. music:Add("What do you call the words of a song?", {"Lyrics", "Melody", "Harmony", "Rhythm"})
  1186. music:Add("Which of these is not a wind instrument?", {"Violin", "Flute", "Clarinet", "Saxophone"})
  1187. music:Add("What is the national anthem of the United States called?", {"The Star Spangled Banner", "God Save the Queen", "La Marseillaise", "O Canada"})
  1188. music:Add("Which of these is not a type of guitar?", {"Cello", "Acoustic", "Electric", "Bass"}, 2)
  1189.  
  1190. local brainrot = categoryManager.New("Brainrot", "easy")
  1191. brainrot:Add("What's the word for those strange little men stuck in toilets?", {"Skibidi", "Toilet snakes", "Plumbing gnomes", "Bowl boys"})
  1192. brainrot:Add("What is it called when someone talks at lenght in an irritating manner?", {"Yapping", "Woofing", "Ranting", "Monologuing"})
  1193. brainrot:Add("Which term describes someone who's a bit of a lone wolf—perhaps even better than an alpha?", {"Sigma", "Omega", "Beta", "Zeta"})
  1194. brainrot:Add("In which U.S. state are wild, strange, unfortunate things most likely to happen, according to the internet?", {"Only in Ohio", "Only in Alaska", "Only in Florida", "Only in Mississippi"})
  1195. brainrot:Add("What is the name of the purple McDonad's drink?", {"Grimace Shake", "Sussy Punch", "Blueberry McFlurry", "Expired Shake"}, 2)
  1196.  
  1197. local brainrot2 = categoryManager.New("Brainrot", "easy")
  1198. brainrot2:Add("What does it mean to be 'cooked'?", {"In a really bad position", "Extremely hungry", "Feeling very tired", "Excited or thrilled"})
  1199. brainrot2:Add("Fill in the blank: 'Erm, what the ___'?", {"sigma", "dog", "heck", "alpha"})
  1200. brainrot2:Add("What is the term for excessively praising or complimenting someone?", {"Glazing", "Rizzing", "Gassing", "Fawning"})
  1201. brainrot2:Add("Who is the main enemy of Skibidi Toilet?", {"Cameraman", "Janitor", "Skibidi Sink", "G-Man"}, 2)
  1202. brainrot2:Add("Finish the quote: 'Just put the ___ in the bag.'", {"Fries", "Money", "Skibidi", "Rizz"})
  1203.  
  1204. local gamerWords = categoryManager.New("Gamer Words", "easy")
  1205. gamerWords:Add("What does 'GG' mean?", {"Good Game", "Get Good", "Great Goal", "Gamer Girl"})
  1206. gamerWords:Add("Which phrase tells players to take a real-world break and go outside?", {"Touch Grass", "Eat a Salad", "Get Off", "Chillax"})
  1207. gamerWords:Add("A 'Noob' is a:", {"New Player", "Night Ops Bot", "Cheater", "Nintendo Spectator"})
  1208. gamerWords:Add("'OP' stands for:", {"Overpowered", "Original Poster", "Online Player", "Open Party"})
  1209. gamerWords:Add("If someone is 'Buffing', they are:", {"Powering Up", "Working Out", "Cleaning Gear", "Taking Damage"}, 2)
  1210.  
  1211. local gamerWords2 = categoryManager.New("Gamer Words", "easy")
  1212. gamerWords2:Add("When devs weaken a strong weapon, they ___ it:", {"Nerf", "Buff", "Patch", "Hotfix"})
  1213. gamerWords2:Add("'Camping' in shooters means:", {"Staying in One Spot", "Cheating", "Co-op Mode", "Building Camps"})
  1214. gamerWords2:Add("A 'Clutch' is a:", {"Last-Second Win", "Car Part", "Team Carry", "Controller Glitch"})
  1215. gamerWords2:Add("If a player is trying to beat a game as fast as possible, they are attempting a ...", {"Speedrun", "Speed Challenge", "Time Trial", "Time attack"})
  1216. gamerWords2:Add("What is the act of pulling enemies away to fight them individually called?", {"Kiting", "Buffing", "Turtling", "Zerging"}, 2)
  1217.  
  1218. local streaming = categoryManager.New("Streaming Culture", "medium")
  1219. streaming:Add("'IRL' stands for:", {"In Real Life", "Internet Rules List", "Item Reward Level", "Instant Raid Loot"})
  1220. streaming:Add("The 'PogChamp' emote expresses:", {"Excitement", "Disappointment", "Anger", "Sadness"})
  1221. streaming:Add("A viewer who watches silently without chatting is called a:", {"Lurker", "NPC", "Nonchatter", "Sneaker"})
  1222. streaming:Add("You can Cheer to streamers using which currency?", {"Bits", "Coins", "Credits", "Super Chats"})
  1223. streaming:Add("What is the term for when a streamer sends their viewers to another live stream?", {"Raid", "Swap", "Relay", "Transfer"}, 2)
  1224.  
  1225. local computerHardware = categoryManager.New("Computer Hardware", "medium")
  1226. computerHardware:Add("What component is considered the 'brain' of a computer?", {"CPU", "RAM", "GPU", "HDD"})
  1227. computerHardware:Add("What does GPU stand for?", {"Graphics Processing Unit", "General Processing Unit", "Gaming Performance Unit", "Global Processing Unit"})
  1228. computerHardware:Add("What unit is computer memory (RAM) typically measured in?", {"Gigabytes", "Volts", "Hertz", "Watts"})
  1229. computerHardware:Add("What is the standard port for connecting a monitor?", {"HDMI", "USB", "Ethernet", "PS/2"})
  1230. computerHardware:Add("Which type of memory loses all its data when the computer is turned off?", {"RAM", "HDD", "SSD", "ROM"}, 2)
  1231.  
  1232. local ancientCivilizations = categoryManager.New("Ancient Civilizations", "medium")
  1233. ancientCivilizations:Add("Which ancient civilization had gladiator fights?", {"Romans", "Greeks", "Egyptians", "Chinese"})
  1234. ancientCivilizations:Add("Which ancient civilization built the pyramids of Giza?", {"Egypt", "Inca", "Rome", "Greece"})
  1235. ancientCivilizations:Add("What was the capital of the Roman Empire?", {"Rome", "Athens", "Alexandria", "Constantinople"})
  1236. ancientCivilizations:Add("Which civilization invented democracy?", {"Ancient Greece", "Rome", "Egypt", "Persia"}, 2)
  1237. ancientCivilizations:Add("Which ancient civilization invented paper?", {"Chinese", "Egyptians", "Greeks", "Persians"})
  1238.  
  1239. local worldRecords = categoryManager.New("World Records", "easy")
  1240. worldRecords:Add("What is the fastest land animal?", {"Cheetah", "Lion", "Horse", "Antelope"})
  1241. worldRecords:Add("Which book holds the record for being the most sold and translated in history?", {"The Bible", "The Lord of the Rings", "Harry Potter and the Sorcerer's Stone", "The Quran"})
  1242. worldRecords:Add("Which is the largest country by land area?", {"Russia", "China", "United States", "Canada"})
  1243. worldRecords:Add("Which is the highest mountain in the world?", {"Mount Everest", "K2", "Mount Kilimanjaro", "Mount Fuji"})
  1244. worldRecords:Add("What is the longest word in a major dictionary?", {"Pneumonoultramicroscopicsilicovolcanoconiosis", "Supercalifragilisticexpialidocious", "Hippopotomonstrosesquippedaliophobia", "Floccinaucinihilipilification"}, 2)
  1245.  
  1246. end -- End of quiz definitions
  1247.  
  1248. -- Adds trailing zeros to a number to make it a certain length.
  1249. -- For example, addTrailingZeros(5, 3) returns "005"
  1250. local function addTrailingZeros(number: number, desiredLength: number): string
  1251.     local numberString = tostring(number)
  1252.     local lenghtDifference = desiredLength - string.len(numberString)
  1253.     return string.rep("0", math.max(0, lenghtDifference))..numberString
  1254. end
  1255.  
  1256. local categoryTable = {} -- Final list names of all quizzes, for example "Geography-easy", "Geography-medium", "Trivia-1", etc.
  1257. local categoryLookupTable = {} -- categoryLookupTable is used to get the actual quiz table from formatted category name
  1258.  
  1259. local function UpdateCategoryTable(categoryName: string, difficulties: table)
  1260.     local multipleDifficulties = categories.numberOfDifficulties[categoryName] > 1
  1261.     for difficulty, quizzes in difficulties do
  1262.         for index, quiz in quizzes do
  1263.             local listName
  1264.             -- if multiple quizzes exist in current difficulty, and there are more difficulties, add their difficulty and index at the end to distinguish them
  1265.             if #quizzes > 1 and multipleDifficulties then
  1266.                 -- Add trailing zeros to prevent cases where a number such as "12" comes before "2"
  1267.                 local finalIndex = addTrailingZeros(index, string.len(#quizzes))
  1268.                 listName = categoryName.."-"..difficulty..finalIndex -- ex: flags-easy1, flags-easy2, flags-medium
  1269.                 table.insert(categoryTable, listName)
  1270.             elseif multipleDifficulties and difficulty ~= "" then -- if multiple difficulties exist but only one quiz in current difficulty, only add the difficulty at the end
  1271.                 listName = categoryName.."-"..difficulty -- ex: flags-hard
  1272.                 table.insert(categoryTable, listName)
  1273.             -- if multiple quizzes exist under the same category, but all under the same difficulty, only add their index at the end
  1274.             elseif #quizzes > 1 or (difficulty == "" and multipleDifficulties) then
  1275.                 local finalIndex = addTrailingZeros(index, string.len(#quizzes))
  1276.                 listName = categoryName.."-"..finalIndex -- ex: history-1, history-2
  1277.                 table.insert(categoryTable, listName)
  1278.             else
  1279.                 listName = categoryName -- ex: flags
  1280.                 table.insert(categoryTable, listName)
  1281.             end
  1282.             categoryLookupTable[listName] = {quiz, difficulty}
  1283.         end
  1284.     end
  1285. end
  1286.  
  1287. for categoryName, difficulties in categories.categoryList do
  1288.     UpdateCategoryTable(categoryName, difficulties)
  1289. end
  1290. table.sort(categoryTable, sortAlphabetically)
  1291.  
  1292. local function SortDifficulty(a, b)
  1293.     local aOrder = table.find(difficultyOrder, a:match("^(%a+)%d*$"))
  1294.     local bOrder = table.find(difficultyOrder, b:match("^(%a+)%d*$"))
  1295.     if not aOrder or not bOrder then
  1296.         return a < b
  1297.     elseif aOrder == bOrder then
  1298.         return a < b
  1299.     else
  1300.         return aOrder < bOrder
  1301.     end
  1302. end
  1303.  
  1304. local categoriesToSend: { string } = {} -- no suffixes, only category names. format should be: food and drink, flags, science
  1305. -- categoriesToSendDetailed sends if settings.sendDetailedCategorylist is true
  1306. local categoriesToSendDetailed: { string } = {} -- format should be: food and drink, flags [easy, easy2, medium, hard], science [easy, hard]
  1307.  
  1308. local function addToSendCategoryTable(categoryName: string, difficulties: { any })
  1309.     local formattedCategory = {}
  1310.     local iterator = 0
  1311.     local multipleDifficulties = categories.numberOfDifficulties[categoryName] > 1
  1312.     for difficulty, quizzes in difficulties do
  1313.         iterator += 1
  1314.         if #quizzes > 1 and multipleDifficulties then
  1315.             for index, quiz in quizzes do
  1316.                 table.insert(formattedCategory, difficulty..index) -- "easy1, easy2"
  1317.             end
  1318.         elseif multipleDifficulties and difficulty == "" then
  1319.             table.insert(formattedCategory, difficulty.."1") -- to make sure that categories without a difficulty also get shown
  1320.         elseif multipleDifficulties then
  1321.             table.insert(formattedCategory, difficulty) -- "easy"
  1322.         elseif #quizzes > 1 then
  1323.             table.insert(formattedCategory, tostring(#quizzes)) -- "2, 4". simply indicates how many quizzes exist in that cateogry
  1324.         end
  1325.     end
  1326.     table.sort(formattedCategory, SortDifficulty) -- make sure easy always comes before medium, etc.
  1327.     if #formattedCategory > 0 then
  1328.         formattedCategory = categoryName.." ["..table.concat(formattedCategory, ", ").."]"
  1329.     else
  1330.         formattedCategory = categoryName
  1331.     end
  1332.     table.insert(categoriesToSendDetailed, formattedCategory)
  1333.     table.insert(categoriesToSend, categoryName)
  1334. end
  1335.  
  1336. for categoryName, difficulties in categories.categoryList do -- indexing categories for sending with sendCategories() function
  1337.     addToSendCategoryTable(categoryName, difficulties)
  1338. end
  1339. table.sort(categoriesToSend, sortAlphabetically)
  1340. table.sort(categoriesToSendDetailed, sortAlphabetically)
  1341.  
  1342. local firstTimeSendingCategories = true -- Roblox often filters the category table the first time you try to send it
  1343. local currentlySending: string? -- gives alert in this format: "[currentlySending] currently being sent"
  1344. local function sendCategories()
  1345.     if not quizRunning and not currentlySending then
  1346.         currentlySending = "Category list is"
  1347.         local waitTime = 5
  1348.         if firstTimeSendingCategories and settings.sendDetailedCategorylist then
  1349.             notify("Warning: possible filtering", "Turn off detailed categories in settings if the category list gets filtered")
  1350.             waitTime = 7
  1351.         end
  1352.         Chat("❓ | Quiz topics:", true)
  1353.         task.wait(3)
  1354.         -- only use the clear filter option when detailed leaderboards are on (more prone to filtering)
  1355.         if settings.sendDetailedCategorylist then
  1356.             splitIntoMessages(categoriesToSendDetailed, ", ", true, waitTime)
  1357.         else
  1358.             splitIntoMessages(categoriesToSend, ", ", false, waitTime)
  1359.         end
  1360.         if settings.sendDetailedCategorylist then
  1361.             firstTimeSendingCategories = false
  1362.         end
  1363.         currentlySending = nil
  1364.     elseif currentlySending then
  1365.         notify(`{currentlySending} currently being sent`, "Wait for it to finish sending before sending the category list")
  1366.     elseif quizRunning then
  1367.         notify("Can't send category list", "Can't send the category list while a quiz is running. Stop the quiz or wait for it to end")
  1368.     end
  1369. end
  1370.  
  1371. local  function IsCategoryEqual(categoryName: string, matchCategory: string, suffix: string): boolean -- true if the categories are the same with the modifies. (WWII-1, WWI) -> false (WWII-1, WWII) -> true
  1372.     if categoryName == matchCategory then
  1373.         return true
  1374.     end
  1375.     local pattern = "^"..EscapePattern(categoryName..suffix)
  1376.     return string.match(matchCategory, pattern) ~= nil
  1377. end
  1378.  
  1379. local function RemoveDuplicateCategories(targetTable: table, categoryName: string, suffix: string)
  1380.     local tempTable = table.clone(targetTable) -- to prevent weird stuff from happening when the loop removes items from the table while it is actively running
  1381.     for i = 1, #targetTable do
  1382.         if IsCategoryEqual(categoryName, targetTable[i], suffix) then -- remove all of the category's quizzes to prevent duplicates
  1383.             table.remove(tempTable, table.find(tempTable, targetTable[i]))
  1384.         end
  1385.     end
  1386.     return tempTable
  1387. end
  1388.  
  1389. local categoryDropdown
  1390. local function UpdateCategory(categoryName: string) -- reindexes the specified category
  1391.     categoriesToSend = RemoveDuplicateCategories(categoriesToSend, categoryName, "")
  1392.     categoriesToSendDetailed = RemoveDuplicateCategories(categoriesToSendDetailed, categoryName, " [")
  1393.     local difficulties = categories.categoryList[categoryName]
  1394.     addToSendCategoryTable(categoryName, difficulties)
  1395.     categoryTable = RemoveDuplicateCategories(categoryTable, categoryName, "-")
  1396.     UpdateCategoryTable(categoryName, difficulties)
  1397. end
  1398.  
  1399. local numberOfCategoriesPerDifficulty: { number } = {}
  1400. for categoryName, category in categoryLookupTable do -- find how many categories are in each difficulty group. Needed so autoplay knows when it's out of categories
  1401.     if not numberOfCategoriesPerDifficulty[category[2]] then
  1402.         numberOfCategoriesPerDifficulty[category[2]] = 1
  1403.     else
  1404.         numberOfCategoriesPerDifficulty[category[2]] += 1
  1405.     end
  1406. end
  1407. ------------- Custom question OOP -------------
  1408. local CustomCategoryManager = {}
  1409. CustomCategoryManager.__index = CustomCategoryManager
  1410. local categoryNames = {}
  1411.  
  1412. local autoplayFilterDifficulties: { string } = {} -- array of difficulties that'll show up on the autoplay filter dropdown
  1413. local autoplayFilterDropdown
  1414. function CustomCategoryManager.New(categoryName: string, difficulty: string?)
  1415.     -- Do sorting and refresh category dropdown only when all custom categories are added
  1416.     CustomCategoryManager.scheduleRefresh()
  1417.     if type(categoryName) == "string" then
  1418.         if type(difficulty) ~= "string" then
  1419.             difficulty = "" -- if difficulty is not a string, use default (blank)
  1420.         else
  1421.             difficulty = string.lower(difficulty)
  1422.         end
  1423.         -- if brand new category is added, add it to the filter dropdown
  1424.         if not numberOfCategoriesPerDifficulty[difficulty] or numberOfCategoriesPerDifficulty[difficulty] == 0 then
  1425.             numberOfCategoriesPerDifficulty[difficulty] = 1
  1426.             if difficulty == "" then
  1427.                  table.insert(autoplayFilterDifficulties, "missing difficulty")
  1428.             else
  1429.                 table.insert(autoplayFilterDifficulties, difficulty)
  1430.             end
  1431.             autoplayFilterDropdown:Refresh(autoplayFilterDifficulties)
  1432.         end
  1433.         -- If a category with the same name already exists, use the existing name
  1434.         for name, _ in categories.categoryList do
  1435.             if string.lower(name) == string.lower(categoryName) then
  1436.                 categoryName = name
  1437.             end
  1438.         end
  1439.         local newCategory = setmetatable(categoryManager.New(categoryName, difficulty), CustomCategoryManager)
  1440.         categoryNames[newCategory] = categoryName
  1441.         UpdateCategory(categoryNames[newCategory])
  1442.         return newCategory
  1443.     else
  1444.         print("Custom quiz error | CategoryName is not a string")
  1445.     end
  1446.     notify("Can't add custom quiz", "See the output for more information")
  1447. end
  1448.  
  1449. function CustomCategoryManager:Add(quesitonText: string, options: { string }, value: number?)
  1450.     if type(quesitonText) == "string" then
  1451.         if type(options) == "table" then
  1452.             if #options > 1 then
  1453.                 if type(value) == "nil" or type(value) == "number" then
  1454.                     categoryManager:Add(quesitonText, options, value, self)
  1455.                     return
  1456.                 else
  1457.                     print("Custom quiz error | [" .. quesitonText .. "] question value needs to be a number or nil")
  1458.                 end
  1459.             else
  1460.                 print("Custom quiz error | [" .. quesitonText .. "] options table need to have more than one option")
  1461.             end
  1462.         else
  1463.             print("Custom quiz error | [" .. quesitonText .. "] options is not a table")
  1464.         end
  1465.     else
  1466.         print("Custom quiz error | [" .. quesitonText .. "] quesitonText is not a string")
  1467.     end
  1468.     notify("Can't add custom question", "See the output for more information")
  1469. end
  1470.  
  1471. -- Schedules a sort & refresh of the category dropdown when custom category adding is done
  1472. function CustomCategoryManager.scheduleRefresh()
  1473.     if not CustomCategoryManager.refreshScheduled then
  1474.         CustomCategoryManager.refreshScheduled = true
  1475.         task.defer(function() -- Defer to run after all custom categories are added
  1476.             -- We're only running this once as it is expensive
  1477.             table.sort(categoriesToSend, sortAlphabetically)
  1478.             table.sort(categoriesToSendDetailed, sortAlphabetically)
  1479.             table.sort(categoryTable, sortAlphabetically)
  1480.             categoryDropdown:Refresh(categoryTable)
  1481.             CustomCategoryManager.refreshScheduled = false
  1482.         end)
  1483.     end
  1484. end
  1485.  
  1486. local function sortUserPoints(type)
  1487.     local array = {}
  1488.     for username, value in pairs(userPoints) do
  1489.         array[#array+1] = {username, value[type], value.DisplayName}
  1490.     end
  1491.     table.sort(array, function(a, b)
  1492.         return a[2] > b[2]
  1493.     end)
  1494.     return array
  1495. end
  1496.  
  1497. local medalEmojis = {"🥇 ", "🥈 ", "🥉 "}
  1498. local function sendLeaderboard(type: string, message: string, triggeredByUser: boolean?)
  1499.     if triggeredByUser and quizRunning then
  1500.         notify("Can't send LB", "Can't send LB while quiz is running. Stop the current quiz or wait for it to end")
  1501.         return
  1502.     elseif currentlySending then
  1503.         notify(`{currentlySending} currently being sent`, "Wait for it to finish sending before sending the leaderboard")
  1504.         return
  1505.     end
  1506.     currentlySending = "A LB is" -- abbreviate to LB to make sure text doesn't cut off in notification title
  1507.     local pointsArray
  1508.     message = message or ""
  1509.     if type == "Current quiz" then
  1510.         pointsArray = sortUserPoints("CurrentQuizPoints")
  1511.     else
  1512.         pointsArray = sortUserPoints("GlobalPoints")
  1513.     end
  1514.     task.wait(1.5)
  1515.     if triggeredByUser and quizRunning then
  1516.         return
  1517.     end
  1518.     for i = 1, 3 do
  1519.         if pointsArray[i] and pointsArray[i][2] > 0 then -- if entry exisits and player has more than 0 points
  1520.             if i == 1 then
  1521.                 Chat(message..type.." leaderboard:")
  1522.             end
  1523.             local username = pointsArray[i][1]
  1524.             local displayName = pointsArray[i][3]
  1525.             local points = roundNumber(pointsArray[i][2], 1)
  1526.             local pointWord = if points > 1 then "points" else "point"
  1527.             task.wait(2.5)
  1528.             if triggeredByUser and quizRunning then
  1529.                 return
  1530.             end
  1531.             Chat(medalEmojis[i]..displayName.." (@"..username..") - "..points.." "..pointWord)
  1532.             UpdateBoothgameTexts(medalEmojis[i]..displayName, true)
  1533.         end
  1534.     end
  1535.     task.wait(2.5)
  1536.     currentlySending = nil
  1537. end
  1538. local leaderboardLabels = {
  1539.     CurrentQuizPoints = {},
  1540.     GlobalPoints = {}
  1541. }
  1542. local function GetPointsAndUpdateLabels(type: string)
  1543.     local pointsArray = sortUserPoints(type)
  1544.     for index, label in leaderboardLabels[type] do
  1545.         if pointsArray[index] and pointsArray[index][2] > 0 then
  1546.             local username = pointsArray[index][1]
  1547.             local displayName = pointsArray[index][3]
  1548.             local points = roundNumber(pointsArray[index][2], 1)
  1549.             local pointWord = if points > 1 then "points" else "point"
  1550.             label:Set(`{medalEmojis[index]} {displayName} (@{username}) - {points} {pointWord}`)
  1551.         else
  1552.             label:Set(medalEmojis[index].." [Empty] - 0")
  1553.         end
  1554.     end
  1555. end
  1556. ClearLeaderboardLabels = function(type: string) -- clears current quiz points in the UI leaderboard at the beginning of each quiz
  1557.     for index, label in leaderboardLabels[type] do
  1558.         label:Set(medalEmojis[index].." [Empty] - 0")
  1559.     end
  1560. end
  1561. UpdateUILeaderboard = function(type: string?) -- updates the labels for the leaderboard in the UI
  1562.     type = type or "All"
  1563.     if type == "All" then
  1564.         GetPointsAndUpdateLabels("GlobalPoints")
  1565.         GetPointsAndUpdateLabels("CurrentQuizPoints")
  1566.     else
  1567.         assert(leaderboardLabels[type], "Invalid leaderboard type")
  1568.         GetPointsAndUpdateLabels(type)
  1569.     end
  1570. end
  1571.  
  1572. local autoplayChosenCategories = {} -- categories previously chosen by autoplay
  1573. local autoPlayDifficulties = difficultyOrder -- allowed difficulties by autoplay. Default is all difficulties
  1574. local function choseAutoplayCategory()
  1575.     local autoplayComplete = true -- true if every category has been played
  1576.     for _, difficulty in autoPlayDifficulties do -- do this each time in case the user changes the autoplay filter
  1577.         if autoplayChosenCategories[difficulty] and not (#autoplayChosenCategories[difficulty] == numberOfCategoriesPerDifficulty[difficulty]) then
  1578.             autoplayComplete = false
  1579.             break
  1580.         -- if the difficulty has not been chosen yet at all or there are no quizzes in it
  1581.         elseif not autoplayChosenCategories[difficulty] and numberOfCategoriesPerDifficulty[difficulty] then
  1582.             autoplayComplete = false
  1583.             break
  1584.         end
  1585.     end
  1586.     if autoplayComplete then -- if every category has been chosen, clear the chosencategories table
  1587.         table.clear(autoplayChosenCategories)
  1588.     end
  1589.     local chosenCategory = categoryTable[math.random(#categoryTable)]
  1590.     local chosenCategoryDifficulty = categoryLookupTable[chosenCategory][2]
  1591.     if (autoplayChosenCategories[chosenCategoryDifficulty] and table.find(autoplayChosenCategories[chosenCategoryDifficulty], chosenCategory)) or not table.find(autoPlayDifficulties, categoryLookupTable[chosenCategory][2]) then -- if category has been previously chosen, chose another category
  1592.         return choseAutoplayCategory()
  1593.     else
  1594.         return chosenCategory
  1595.     end
  1596. end
  1597.  
  1598. local function isFreeToRunQuiz(): boolean
  1599.     if quizRunning then
  1600.         notify("A quiz is currently running", "Stop the current quiz or wait for it to end")
  1601.         return false
  1602.     elseif quizCooldown then
  1603.         notify("Cooldown active", "Try again in a few seconds")
  1604.         return false
  1605.     end
  1606.     return true
  1607. end
  1608.  
  1609. local function startQuiz(category: string)
  1610.     if not isFreeToRunQuiz() then
  1611.         return
  1612.     end
  1613.     SuppressBoothGameNotifications(true)
  1614.     CaptureBoothTextAndImage()
  1615.     quizRunning = true
  1616.     pointManager.ClearQuizPoints()
  1617.     Chat('🚀 | Initiating "'..category..'" quiz...')
  1618.     UpdateBoothgameTexts(category)
  1619.     task.wait(3)
  1620.     local loopIterations = 0
  1621.     for _, v in pairs(categoryLookupTable[category][1]) do
  1622.         if not quizRunning then
  1623.             return
  1624.         end
  1625.         local questionAsked = v:Ask()
  1626.         if questionAsked == false then -- if question didn't get asked because of the filter, skip to next
  1627.             currentQuestion = nil
  1628.             questionAnsweredBy = nil
  1629.             task.wait(5)
  1630.             continue
  1631.         end
  1632.         awaitAnswer(v)
  1633.         if not quizRunning then
  1634.             return
  1635.         end
  1636.         task.wait(6)
  1637.         loopIterations += 1
  1638.         if not quizRunning then
  1639.             return
  1640.         end
  1641.         if loopIterations == settings.sendLeaderBoardAfterQuestions and settings.automaticLeaderboards and settings.automaticCurrentQuizLeaderboard then
  1642.             sendLeaderboard("Current quiz", "📜 | ")
  1643.             loopIterations = 0
  1644.         end
  1645.     end
  1646.     task.wait(2)
  1647.     if loopIterations ~= 0 and settings.automaticLeaderboards and settings.automaticCurrentQuizLeaderboard then
  1648.         sendLeaderboard("Current quiz", "📜 | ")
  1649.     end
  1650.     UpdateBoothgameTexts(endMessage, true)
  1651.     if settings.automaticLeaderboards and settings.automaticServerQuizLeaderboard then
  1652.         sendLeaderboard("Server", "🏆 | Quiz ended. ")
  1653.         task.wait(2)
  1654.     else
  1655.         SendMessageWhenReady("🏁 | Quiz ended")
  1656.         task.wait(3)
  1657.     end
  1658.     task.delay(8, function()
  1659.         if not quizRunning then
  1660.             UpdateBoothgameTexts("", true)
  1661.             task.wait(6)
  1662.             SuppressBoothGameNotifications(false)
  1663.         end
  1664.     end)
  1665.     quizRunning = false
  1666.     if settings.autoplay then
  1667.         if not autoplayChosenCategories[categoryLookupTable[category][2]] then -- insert the category into autoplayChosenCategories[difficulty]
  1668.             autoplayChosenCategories[categoryLookupTable[category][2]] = {category}
  1669.         else
  1670.             table.insert(autoplayChosenCategories[categoryLookupTable[category][2]], category)
  1671.         end
  1672.         UpdateBoothgameTexts("🎲 Picking next category...", true)
  1673.         Chat("🎲 | Picking next category...")
  1674.         local chosenCategory = choseAutoplayCategory()
  1675.         task.wait(6)
  1676.         startQuiz(chosenCategory)
  1677.     else
  1678.         UpdateBoothgameTexts(endMessage, true)
  1679.     end
  1680. end
  1681.  
  1682. local quizModeRules = {
  1683.     "Each question has one correct answer and one or more wrong answers. You can submit your answers only through text chat, not voice.",
  1684.     "If you submit an incorrect answer, you will always have to wait "..tostring(settings.userCooldown).." seconds before you can submit another answer.",
  1685.     "If you submit a correct answer, you will earn points.",
  1686. }
  1687. local kahootModeRules = {
  1688.     "Each question has one correct answer and one or more wrong answers. You can submit your answers only through text chat, not voice.",
  1689.     "You can only submit ONE answer per question.", "The first answer you submit is your final answer, and it can not be changed.",
  1690.     "You have "..tostring(settings.questionTimeout).." seconds to answer the question after all the options have been said.",
  1691.     "Every second after all the options have been said, the points you will gain for answering correctly decrease.",
  1692.     "In other words, the quicker you answer, the more points you will gain.",
  1693.     "Additionally, the first person who submits a correct answer gets 1.5x points.",
  1694. }
  1695. local function sendRules()
  1696.     if quizRunning then
  1697.         notify("Can't send rules", "Can't send rules while quiz is running. Stop the current quiz or wait for it to end")
  1698.         return
  1699.     elseif currentlySending then
  1700.         notify(`{currentlySending} currently being sent`, "Wait for it to finish sending before sending the rules")
  1701.         return
  1702.     end
  1703.     currentlySending = "The rules are"
  1704.     if mode == "Quiz" then
  1705.         Chat("📜 | Quiz mode rules:", true)
  1706.         task.wait(2)
  1707.         splitIntoMessages(quizModeRules, " ", 2)
  1708.     elseif mode == "Kahoot" then
  1709.         Chat("📜 | Kahoot mode rules:", true)
  1710.         task.wait(2)
  1711.         splitIntoMessages(kahootModeRules, " ")
  1712.     end
  1713.     currentlySending = nil
  1714. end
  1715.  
  1716. players.PlayerRemoving:Connect(function(player) -- remove player's userpoint account on leave
  1717.     if settings.removeLeavingPlayersFromLB then
  1718.         pointManager.RemoveAccount(player)
  1719.     end
  1720. end)
  1721.  
  1722. local function getPlayerByPlayerName(name)
  1723.     if name then
  1724.         name = string.lower(name)
  1725.         for i, v in ipairs(players:GetPlayers()) do
  1726.             if string.lower(string.sub(v.Name, 1, #name)) == name then
  1727.                 return v
  1728.             end
  1729.             if string.lower(string.sub(v.DisplayName, 1, #name)) == name then
  1730.                 return v
  1731.             end
  1732.         end
  1733.         for i, v in ipairs(players:GetPlayers()) do
  1734.             if string.match(v.Name:lower(), name) then
  1735.                 return v
  1736.             end
  1737.             if string.match(v.DisplayName:lower(), name) then
  1738.                 return v
  1739.             end
  1740.         end
  1741.     end
  1742. end
  1743.  
  1744. local function getTargetPlayer(name) -- try to get a target player from the name
  1745.     local target
  1746.     local matchingPlayer = getPlayerByPlayerName(name)
  1747.     if name:lower() == "me" then
  1748.         target = localPlayer
  1749.         return target
  1750.     elseif matchingPlayer then
  1751.         target = matchingPlayer
  1752.         return target
  1753.     end
  1754.     target = nil
  1755.     return target
  1756. end
  1757.  
  1758. local function getCategoryName(name: string) -- detects category from begging of string, for example: "gene" will return "general" category
  1759.     if #name < 2 then
  1760.         return
  1761.     end
  1762.     name = name:lower()
  1763.     for _, category in categoryTable do
  1764.         if string.lower(string.sub(category, 1, #name)) == name then
  1765.             return category
  1766.         end
  1767.     end
  1768. end
  1769. ---------- UI ----------
  1770. local library, window
  1771.  
  1772. setgenv("DISABLE_RAYFIELD_REQUESTS", true) -- Disables Rayfield's analytic reports
  1773. setgenv("rayfieldCached", true) -- Disables Rayfield's splash screen
  1774. local success, errorMessage = pcall(function()
  1775.     library = loadstring(game:HttpGet('https://raw.githubusercontent.com/Damian-11/Rayfield/stable/source.lua'))() -- Rayfield lib
  1776.     window = library:CreateWindow({
  1777.         Name = "quizbot | Made by Damian11 AKA Quizzer",
  1778.         LoadingTitle = "Loading quizbot...",
  1779.         LoadingSubtitle = "made by Damian11 AKA Quizzer",
  1780.         ShowText = "quizbot",
  1781.         DisableRayfieldPrompts = true,
  1782.         DisableBuildWarnings = true,
  1783.     })
  1784. end)
  1785. if not success then -- if the libary is down for any reason, ask user to report it on discord
  1786.     notify("quizbot error", "Failed to load UI libary. Please report this bug in the Discord server:", {
  1787.         Callback = setclipboard("https://discord.gg/wm384KFFMC"),
  1788.         Button1 = "Copy invite link"
  1789.     })
  1790.     warn("quizbot error [UI LIB] | " .. errorMessage)
  1791.     return -- stops the script
  1792. end
  1793.  
  1794. local function toggleUI(_actionName, inputState, _inputObject)
  1795.     if inputState == Enum.UserInputState.Begin then
  1796.         library:SetVisibility(not library:IsVisible())
  1797.     end
  1798. end
  1799.  
  1800. -- Remember that laptops with touch screens exist
  1801. if UserInputService.TouchEnabled and not UserInputService.KeyboardEnabled then
  1802.     ContextActionService:BindAction("ToggleUI", toggleUI, true)
  1803.     ContextActionService:SetImage("ToggleUI", "rbxassetid://100698995249426")
  1804. end
  1805.  
  1806. local mainTab = window:CreateTab("Main", 74002778429106)
  1807.  
  1808. if outdated and not getDataFileValue("disableVersionAlert") then
  1809.     mainTab:CreateSection("Outdated version warning")
  1810.     mainTab:CreateParagraph({
  1811.         Title = "Outdated quizbot version",
  1812.         Content = "You are using an outdated version of quizbot. Use the latest version to access new features, updated quizzes, and more. [Disable in settings]"
  1813.     })
  1814.     mainTab:CreateButton({
  1815.         Name = "Click this button copy the latest version of quizbot",
  1816.         Callback = copyLatestScript,
  1817.     })
  1818. end
  1819.  
  1820. mainTab:CreateSection("Category selection")
  1821. local categoryLabel
  1822. local selectedCategory
  1823. mainTab:CreateInput({
  1824.     Name = "Category",
  1825.     PlaceholderText = "Enter a category name",
  1826.     RemoveTextAfterFocusLost = false,
  1827.     Callback = function(value)
  1828.         selectedCategory = getCategoryName(value)
  1829.         if selectedCategory then
  1830.             categoryLabel:Set("Selected category: "..selectedCategory)
  1831.             categoryDropdown:Set({selectedCategory})
  1832.         end
  1833.     end
  1834. })
  1835. categoryDropdown = mainTab:CreateDropdown({
  1836.     Name = "Category",
  1837.     Options = categoryTable,
  1838.     CurrentOption = "Select a category",
  1839.     Callback = function(option)
  1840.         selectedCategory = option[1]
  1841.         categoryLabel:Set("Selected category: "..selectedCategory)
  1842.     end
  1843. })
  1844. mainTab:CreateButton({
  1845.     Name = "Send category list in chat",
  1846.     Callback = sendCategories
  1847. })
  1848.  
  1849. mainTab:CreateSection("Quiz controls")
  1850. categoryLabel = mainTab:CreateLabel("Selected category: None")
  1851. mainTab:CreateButton({
  1852.     Name = "Start quiz",
  1853.     Callback = function()
  1854.         if categoryLookupTable[selectedCategory] then
  1855.             startQuiz(selectedCategory)
  1856.         else
  1857.             notify("Invalid category", "Select a valid category to start the quiz")
  1858.         end
  1859.     end
  1860. })
  1861. mainTab:CreateButton({
  1862.     Name = "Stop quiz",
  1863.     Callback = function()
  1864.         if quizRunning then
  1865.             quizCooldown = true
  1866.             quizRunning = false
  1867.             currentQuestion = nil
  1868.             questionAnsweredBy = nil
  1869.             awaitingAnswer = false
  1870.             task.delay(5, function()
  1871.                 quizCooldown = false
  1872.             end)
  1873.         end
  1874.     end
  1875. })
  1876.  
  1877. mainTab:CreateSection("Autoplay")
  1878. mainTab:CreateToggle({
  1879.     Name = "Autoplay quizzes automatically",
  1880.     CurrentValue = settings.autoplay,
  1881.     Callback = function(value)
  1882.         settings.autoplay = value
  1883.         EnableAntiAfk()
  1884.     end
  1885. })
  1886.  
  1887. -- only include difficulties which include categories
  1888. for _, difficulty: string in difficultyOrder do
  1889.     if numberOfCategoriesPerDifficulty[difficulty] and numberOfCategoriesPerDifficulty[difficulty] > 0 then
  1890.         if difficulty == "" then -- replace "" with "missing difficulty" to prevent confusion
  1891.             difficulty = "missing difficulty"
  1892.         end
  1893.         table.insert(autoplayFilterDifficulties, difficulty)
  1894.     end
  1895. end
  1896. autoplayFilterDropdown = mainTab:CreateDropdown({
  1897.     Name = "Autoplay filter (unselect all to disable filter)",
  1898.     Options = autoplayFilterDifficulties,
  1899.     MultipleOptions = true,
  1900.     Callback = function(options)
  1901.         local filteredTable = table.clone(options) -- since tables are passed by value (mutable), they need to be cloned to prevent modifying the original table
  1902.         if #options == 0 then -- if none selected, select all
  1903.             filteredTable = difficultyOrder
  1904.         elseif table.find(filteredTable, "missing difficulty") then
  1905.             filteredTable[table.find(filteredTable, "missing difficulty")] = ""
  1906.         end
  1907.         autoPlayDifficulties = filteredTable
  1908.     end
  1909. })
  1910. mainTab:CreateButton({
  1911.     Name = "Play random quiz",
  1912.     Callback = function()
  1913.         if isFreeToRunQuiz() then
  1914.             local chosenCategory = choseAutoplayCategory()
  1915.             notify("Random quiz chosen", `Starting {chosenCategory} quiz`)
  1916.             startQuiz(chosenCategory)
  1917.         end
  1918.     end
  1919. })
  1920.  
  1921. local leaderboardTab = window:CreateTab("Leaderboard", 97885193604839)
  1922. leaderboardTab:CreateSection("Server leaderboard (doesn't reset)")
  1923. for i = 1, 3 do
  1924.     table.insert(leaderboardLabels.GlobalPoints, leaderboardTab:CreateLabel(medalEmojis[i].." [Empty] - 0"))
  1925. end
  1926. leaderboardTab:CreateButton({
  1927.     Name = "Send server leaderboard in chat",
  1928.     Callback = function()
  1929.         sendLeaderboard("Server", "🏆 | ", true)
  1930.     end
  1931. })
  1932.  
  1933. leaderboardTab:CreateSection("Current quiz leaderboard (resets every quiz)")
  1934. for i = 1, 3 do
  1935.     table.insert(leaderboardLabels.CurrentQuizPoints, leaderboardTab:CreateLabel(medalEmojis[i].." [Empty] - 0"))
  1936. end
  1937. leaderboardTab:CreateButton({
  1938.     Name = "Send current quiz leaderboard in chat",
  1939.     Callback = function()
  1940.         sendLeaderboard("Current quiz", "📜 | ", true)
  1941.     end
  1942. })
  1943.  
  1944. leaderboardTab:CreateSection("Reset points")
  1945. leaderboardTab:CreateButton({
  1946.     Name = "Reset all points",
  1947.     Callback = pointManager.ResetAllPoints
  1948. })
  1949.  
  1950. local playerControlTab = window:CreateTab("Player controls", 100068360107422)
  1951. local targetPlayer
  1952. local targetPlayerLabel
  1953. playerControlTab:CreateSection("Select target")
  1954. playerControlTab:CreateInput({
  1955.     Name = "Target",
  1956.     PlaceholderText = "Enter target name",
  1957.     Callback = function(value)
  1958.         if #value < 1 then return end
  1959.         targetPlayer = getTargetPlayer(value)
  1960.         if targetPlayer then
  1961.             targetPlayerLabel:Set(`Target: {targetPlayer.DisplayName} (@{targetPlayer.Name})`)
  1962.         else
  1963.             targetPlayerLabel:Set("Target: None")
  1964.         end
  1965.     end
  1966. })
  1967.  
  1968. local function TargetExists(target)
  1969.     if not target then
  1970.         notify("Invalid target", "Specify a target player")
  1971.         return false
  1972.     else
  1973.         return true
  1974.     end
  1975. end
  1976.  
  1977. targetPlayerLabel = playerControlTab:CreateLabel("Target: None")
  1978. playerControlTab:CreateSection("Modify points")
  1979. local pointsToAdd
  1980. playerControlTab:CreateInput({
  1981.     Name = "Amount of points",
  1982.     PlaceholderText = "Enter points amount",
  1983.     Callback = function(value)
  1984.         if value and tonumber(value) then
  1985.             pointsToAdd = value
  1986.         end
  1987.     end
  1988. })
  1989. playerControlTab:CreateButton({
  1990.     Name = "Apply points",
  1991.     Callback = function()
  1992.         if not TargetExists(targetPlayer) then
  1993.             return
  1994.         end
  1995.         if pointsToAdd then
  1996.             pointManager.AddPoints(targetPlayer, pointsToAdd, "GlobalPoints")
  1997.             notify("Points added", pointsToAdd.." server points have been added to "..targetPlayer.Name)
  1998.         else
  1999.             notify("Enter a point amount", "Enter an amount of points to add")
  2000.         end
  2001.     end
  2002. })
  2003. playerControlTab:CreateButton({
  2004.     Name = "Remove all points from player",
  2005.     Callback = function()
  2006.         if not TargetExists(targetPlayer) then return end
  2007.         pointManager.RemoveAccount(targetPlayer)
  2008.         notify("Points reset", "Successfully removed all points from "..targetPlayer.Name)
  2009.     end
  2010. })
  2011.  
  2012. playerControlTab:CreateSection("Block")
  2013. playerControlTab:CreateButton({
  2014.     Name = "Block from participating",
  2015.     Callback = function()
  2016.         if not TargetExists(targetPlayer) then return end
  2017.         if not table.find(blockedPlayers, targetPlayer.Name) then
  2018.             table.insert(blockedPlayers, targetPlayer.Name)
  2019.             notify("Player blocked", targetPlayer.Name.." has been blocked from participating")
  2020.         else
  2021.             notify("Can't block player", targetPlayer.Name.." is already blocked from participating")
  2022.         end
  2023.     end
  2024. })
  2025. playerControlTab:CreateButton({
  2026.     Name = "Unblock from participating",
  2027.     Callback = function()
  2028.         if not TargetExists(targetPlayer) then return end
  2029.         if table.find(blockedPlayers, targetPlayer.Name) then
  2030.             table.remove(blockedPlayers, table.find(blockedPlayers, targetPlayer.Name))
  2031.             notify("Player unblocked", targetPlayer.Name.." is no longer blocked from participating")
  2032.         else
  2033.             notify("Can't unblock player", targetPlayer.Name.." is not blocked from participating")
  2034.         end
  2035.     end
  2036. })
  2037. playerControlTab:CreateButton({
  2038.     Name = "Unblock all",
  2039.     Callback = function()
  2040.         notify("Unblocked everyone", #blockedPlayers.." players have been unblocked")
  2041.         table.clear(blockedPlayers)
  2042.     end
  2043. })
  2044.  
  2045. playerControlTab:CreateSection("Whitelist")
  2046. playerControlTab:CreateToggle({
  2047.     Name = "Enable whitelist",
  2048.     CurrentValue = whiteListEnabled,
  2049.     Callback = function(value)
  2050.         whiteListEnabled = value
  2051.     end
  2052. })
  2053. playerControlTab:CreateButton({
  2054.     Name = "Whitelist",
  2055.     Callback = function()
  2056.         if not TargetExists(targetPlayer) then return end
  2057.         if not table.find(whiteListedplayers, targetPlayer.Name) then
  2058.             table.insert(whiteListedplayers, targetPlayer.Name)
  2059.             if whiteListEnabled then
  2060.                 notify("Player whitelisted", targetPlayer.Name.." has been whitelisted. The whitelist is currently enabled")
  2061.             else
  2062.                 notify("Player whitelisted", targetPlayer.Name.." has been whitelisted, but the whitelist is currently disabled")
  2063.             end
  2064.         else
  2065.             notify("Can't whitelist player", targetPlayer.Name.." is already on the whitelist")
  2066.         end
  2067.     end
  2068. })
  2069. playerControlTab:CreateButton({
  2070.     Name = "Unwhitelist",
  2071.     Callback = function()
  2072.         if not TargetExists(targetPlayer) then return end
  2073.         if table.find(whiteListedplayers, targetPlayer.Name) then
  2074.             table.remove(whiteListedplayers, table.find(whiteListedplayers, targetPlayer.Name))
  2075.             notify("Player removed", targetPlayer.Name.." is no longer on the whitelist")
  2076.         else
  2077.             notify("Can't remove player", targetPlayer.Name.." is not on the whitelist")
  2078.         end
  2079.     end
  2080. })
  2081. playerControlTab:CreateButton({
  2082.     Name = "Clear whitelist",
  2083.     Callback = function()
  2084.         notify("Whitelist has been cleared", #whiteListedplayers.." players have been removed from the whitelist")
  2085.         table.clear(whiteListedplayers)
  2086.     end
  2087. })
  2088.  
  2089. local settingsTab = window:CreateTab("Settings", 112502172419483)
  2090. settingsTab:CreateSection("Discord server")
  2091. settingsTab:CreateLabel("Join my Discord server: discord.gg/wm384KFFMC")
  2092. settingsTab:CreateButton({
  2093.     Name = "Click this button to copy the invite link",
  2094.     Callback = function()
  2095.         setclipboard("https://discord.gg/wm384KFFMC")
  2096.         notify("Successfully copied invite", "The invite link has been copied to your clipboard")
  2097.     end
  2098. })
  2099.  
  2100. settingsTab:CreateSection("Select mode")
  2101. settingsTab:CreateDropdown({
  2102.     Name = "Mode",
  2103.     Options = {"Quiz", "Kahoot"},
  2104.     CurrentOption = mode,
  2105.     Callback = function(option)
  2106.         mode = option[1]
  2107.         if mode == "Quiz" then
  2108.             Chat("❓ | Quiz mode enabled", true)
  2109.         elseif mode == "Kahoot" then
  2110.             Chat("✉️ | Kahoot mode enabled", true)
  2111.         end
  2112.     end
  2113. })
  2114. settingsTab:CreateButton({
  2115.     Name = "Send rules in chat",
  2116.     Callback = sendRules
  2117. })
  2118.  
  2119. settingsTab:CreateSection("Time settings")
  2120. settingsTab:CreateInput({
  2121.     Name = "Question timeout",
  2122.     PlaceholderText = settings.questionTimeout,
  2123.     Callback = function(value)
  2124.         if value and tonumber(value) then
  2125.             settings.questionTimeout = tonumber(value)
  2126.         end
  2127.     end
  2128. })
  2129. settingsTab:CreateInput({
  2130.     Name = "User cooldown on wrong answer",
  2131.     PlaceholderText = settings.userCooldown,
  2132.     Callback = function(value)
  2133.         if value and tonumber(value) then
  2134.             settings.userCooldown = tonumber(value)
  2135.         end
  2136.     end
  2137. })
  2138.  
  2139. settingsTab:CreateSection("Leaderboard settings")
  2140.  
  2141. settingsTab:CreateToggle({
  2142.     Name = "Remove leaving players from leaderboard",
  2143.     CurrentValue = settings.removeLeavingPlayersFromLB,
  2144.     Callback = function(value)
  2145.         settings.removeLeavingPlayersFromLB = value
  2146.     end
  2147. })
  2148. settingsTab:CreateInput({
  2149.     Name = "Send current quiz leaderboard every X questions",
  2150.     PlaceholderText = settings.sendLeaderBoardAfterQuestions,
  2151.     Callback = function(value)
  2152.         if value and tonumber(value) then
  2153.             settings.sendLeaderBoardAfterQuestions = tonumber(value)
  2154.             if not settings.automaticCurrentQuizLeaderboard then
  2155.                 notify("Current quiz LB is disabled", "This settings doesn't take effect while the automatic sending of the current quiz LB is diabled")
  2156.             end
  2157.         end
  2158.     end
  2159. })
  2160.  
  2161. settingsTab:CreateDivider()
  2162. -- This toggles both disable leaderboard toggles to the "on" position when the disable both leaderboards switch is toggled on
  2163. -- When it is toggled back off, the leaderboard toggles are returned back to their previous positions
  2164. local disableBothLeaderboardsToggle, disableCurrentLeaderboardToggle, disableServerLeaderboardToggle
  2165. local originalSettings: { boolean } = {not settings.automaticCurrentQuizLeaderboard, not settings.automaticServerQuizLeaderboard}
  2166. disableBothLeaderboardsToggle = settingsTab:CreateToggle({
  2167.     Name = "Disable automatic sending of both leaderboards",
  2168.     CurrentValue = not settings.automaticLeaderboards,
  2169.     Callback = function(value)
  2170.         settings.automaticLeaderboards = not value
  2171.         if value then
  2172.             -- Save current toggle positions
  2173.             originalSettings = {not settings.automaticCurrentQuizLeaderboard, not settings.automaticServerQuizLeaderboard}
  2174.             disableCurrentLeaderboardToggle:Set(true)
  2175.             disableServerLeaderboardToggle:Set(true)
  2176.         else
  2177.             disableCurrentLeaderboardToggle:Set(originalSettings[1])
  2178.             disableServerLeaderboardToggle:Set(originalSettings[2])
  2179.         end
  2180.     end
  2181. })
  2182. disableCurrentLeaderboardToggle = settingsTab:CreateToggle({
  2183.     Name = "Disable automatic sending of current quiz LB",
  2184.     CurrentValue = not settings.automaticCurrentQuizLeaderboard or not settings.automaticLeaderboards,
  2185.     Callback = function(value)
  2186.         if value == false and settings.automaticLeaderboards == false then
  2187.             originalSettings = {false, not settings.automaticServerQuizLeaderboard}
  2188.             settings.automaticLeaderboards = true
  2189.             disableBothLeaderboardsToggle:Set(false) -- The toggle's callback gets called when using :Set()
  2190.         end
  2191.         settings.automaticCurrentQuizLeaderboard = not value
  2192.     end
  2193. })
  2194. disableServerLeaderboardToggle = settingsTab:CreateToggle({
  2195.     Name = "Disable automatic sending of server LB",
  2196.     CurrentValue = not settings.automaticServerQuizLeaderboard or not settings.automaticLeaderboards,
  2197.     Callback = function(value)
  2198.         if value == false and settings.automaticLeaderboards == false then
  2199.             originalSettings = {not settings.automaticCurrentQuizLeaderboard, false}
  2200.             settings.automaticLeaderboards = true
  2201.             disableBothLeaderboardsToggle:Set(false)
  2202.         end
  2203.         settings.automaticServerQuizLeaderboard = not value
  2204.     end
  2205. })
  2206.  
  2207. local originalDoNotRepeatState = not settings.repeatTagged
  2208. local doNotRepeatTaggedToggle
  2209. local originalFullModeState = settings.boothGameFullMode
  2210. local fullModeToggle
  2211. if boothGame then
  2212.     settingsTab:CreateSection("Booth game settings")
  2213.     settingsTab:CreateToggle({
  2214.         Name = "Enable sign status (sign item required)",
  2215.         CurrentValue = settings.signStatus,
  2216.         Callback = function(value)
  2217.             settings.signStatus = value
  2218.         end
  2219.     })
  2220.     settingsTab:CreateToggle({
  2221.         Name = "Enable booth status (must own a booth)",
  2222.         CurrentValue = settings.boothStatus,
  2223.         Callback = function(value)
  2224.             settings.boothStatus = value
  2225.             if not value and originalBoothText ~= "" then
  2226.                 UpdateBoothText(originalBoothText)
  2227.             end
  2228.         end
  2229.     })
  2230.     settingsTab:CreateToggle({
  2231.         Name = "Do not send quiz messages in the chat",
  2232.         CurrentValue = settings.disableChat,
  2233.         Callback = function(value)
  2234.             settings.disableChat = value
  2235.             -- Anti-filtering system must be disabled if no messages are being sent
  2236.             if value then
  2237.                 originalDoNotRepeatState = not settings.repeatTagged
  2238.                 doNotRepeatTaggedToggle:Set(true)
  2239.                 originalFullModeState = settings.boothGameFullMode
  2240.                 fullModeToggle:Set(true)
  2241.             else
  2242.                 doNotRepeatTaggedToggle:Set(originalDoNotRepeatState)
  2243.                 fullModeToggle:Set(originalFullModeState)
  2244.             end
  2245.         end
  2246.     })
  2247.     fullModeToggle = settingsTab:CreateToggle({
  2248.         Name = "List answer options on sign and booth",
  2249.         CurrentValue = settings.boothGameFullMode,
  2250.         Callback = function(value)
  2251.             if not value and settings.disableChat then
  2252.                 notify("Can't change setting", 'This setting cannot be changed while the "do not send quiz messages in the chat" setting is enabled')
  2253.                 fullModeToggle:Set(true)
  2254.                 return
  2255.             end
  2256.             settings.boothGameFullMode = value
  2257.         end
  2258.     })
  2259.     settingsTab:CreateToggle({
  2260.         Name = "Use roman numbers for sign timer to bypass filtering",
  2261.         CurrentValue = settings.romanNumbers,
  2262.         Callback = function(value)
  2263.             settings.romanNumbers = value
  2264.         end
  2265.     })
  2266.     settingsTab:CreateToggle({
  2267.         Name = "Disable push notifications while a quiz is in progress",
  2268.         CurrentValue = settings.boothGameSuppressNotifs,
  2269.         Callback = function(value)
  2270.             if not value then
  2271.                 SuppressBoothGameNotifications(false)
  2272.             end
  2273.             settings.boothGameSuppressNotifs = value
  2274.         end
  2275.     })
  2276. end
  2277.  
  2278. settingsTab:CreateSection("Miscellaneous settings")
  2279. doNotRepeatTaggedToggle = settingsTab:CreateToggle({
  2280.     Name = "Do not repeat tagged messages",
  2281.     CurrentValue = not settings.repeatTagged,
  2282.     Callback = function(value)
  2283.         if not value and settings.disableChat then
  2284.             notify("Can't change setting", 'This setting cannot be changed while the "do not send quiz messages in the chat" setting is enabled')
  2285.             doNotRepeatTaggedToggle:Set(true)
  2286.             return
  2287.         end
  2288.         settings.repeatTagged = not value
  2289.         messageFiltered = false
  2290.     end
  2291. })
  2292. settingsTab:CreateToggle({
  2293.     Name = "Send detailed category list in chat (may get tagged)",
  2294.     CurrentValue = settings.sendDetailedCategorylist,
  2295.     Callback = function(value)
  2296.         settings.sendDetailedCategorylist = value
  2297.     end
  2298. })
  2299.  
  2300. if outdated then
  2301.     settingsTab:CreateToggle({
  2302.         Name = "Disable outdated version alerts",
  2303.         CurrentValue = getDataFileValue("disableVersionAlert"),
  2304.         Callback = function(value)
  2305.             writeToDataFile("disableVersionAlert", value)
  2306.         end
  2307.     })
  2308. end
  2309.  
  2310. settingsTab:CreateSection("Disable script")
  2311. settingsTab:CreateButton({
  2312.     Name = "Destory UI and disable script",
  2313.     Callback = function()
  2314.         chatConnection:Disconnect()
  2315.         quizCooldown = true
  2316.         quizRunning = false
  2317.         currentQuestion = nil
  2318.         questionAnsweredBy = nil
  2319.         awaitingAnswer = false
  2320.         ContextActionService:UnbindAction("ToggleUI")
  2321.         library:Destroy()
  2322.         setgenv("QUIZBOT_RUNNING", false)
  2323.     end
  2324. })
  2325.  
  2326. return CustomCategoryManager
Tags: delta
Add Comment
Please, Sign In to add comment