-- Trivia bot port for Verlihub by bourne (bourne@freemail.hu) -- This bot originally was written by Don Leon for PtokaX with LUA version up to 4.0, thank you Leon. -- Note: This is a modified version for the latest LUA (version 5.0.x) -- Now it works in any game mode without limitations. Enjoy. ---------------[ Helper functions ]--------------- -- Send global message to one user function SendGMToUser(data, nick) result, err = VH:SendDataToUser("<" .. BotName .. "> " .. data .. "|", nick) if not result then error("Lua script error: in VH:SendDataToUser ("..err..")") end end -- Send private message to one user function SendPMToUser(data, nick) result, err = VH:SendDataToUser("$To: " .. nick .. " From: " .. BotName .. " $<" .. BotName .. "> " .. data .. "|", nick) if not result then error("Lua script error: in VH:SendDataToUser ("..err..")") end end function SendGMToAll(nick, data) result, err = VH:SendDataToAll("<" .. nick .. "> " .. data .. "|", 1, 10) if not result then error("Lua script error: in VH:SendDataToAll ("..err..")") end end function IsOP(nick) result, data = VH:GetUserClass(nick) if not result then error("Lua script error: in VH:GetUserClass ("..err..")") end if data > 3 then return true else return false end end ---------------[ End helper functions ]--------------- ---------------[ Functions ]--------------- function VH_OnUserLogin(curUser) if (boolAutoLogin) then if (not (playerArray[curUser])) then playerArray[curUser] = 1 else playerArray[curUser] = nil end end return 1 end function VH_OnUserLogout(curUser) playerArray[curUser] = nil return 1 end function VH_OnParsedMsgAny(ip, data) DataArrival(data) return 1 end function DataArrival(data) data = data .. " " -- There is a problem with pattern matching I don't know why... Just add a space to the end. local boolPlaydata = nil if (string.sub(data, 1, 1) == "<") then _, _, curUser = string.find(data, "^<(%S+)>%s+") data = string.sub(data, 1, (string.len(data) - 1)) if ((lngMode == 0) or (lngMode == 1)) then boolPlaydata = 1 end elseif (string.sub(data, 1, 4) == "$To:") then _, _, curUser = string.find(data, "From:%s+(%S+)") data = string.sub(data, 1, (string.len(data) - 1)) local _, _, whoTo = string.find(data,"$To:%s+(%S+)") if (whoTo == BotName) then data = string.sub(data, (15 + string.len(BotName) + string.len(curUser))) if (lngMode == 2) then boolPlaydata = 1 local _, _, chat = string.find(data, "%b<>%s(.+)") SendChatToOthers(chat, curUser) -- This must send PM to every player end end else return end local bOp = IsOP(curUser) local _, _, firstWord = string.find(data, "%b<>%s+(%S+)") if (firstWord ~= nil) then if ((string.lower(firstWord) == string.lower(HelpTrigg)) or (string.lower(firstWord) == string.lower(ExtraHelpTrigg))) then showHelp(curUser) elseif (string.lower(firstWord) == string.lower(SaveTrigg)) then if (bOp) then SaveScores(curUser) end elseif (string.lower(firstWord) == string.lower(LoadTrigg)) then if (bOp) then LoadScores(curUser) end elseif (string.lower(firstWord) == string.lower(TopTrigg)) then TopTen(user) elseif (string.lower(firstWord) == string.lower(AddTrigg)) then if (boolAddQuestion) then s,e,sQuestion,sAnswer = string.find(data, "%b<>%s+%S+%s+(.+)#(.+)") AddQuestion(curUser,sQuestion,sAnswer) elseif (bOp) then s,e,sQuestion,sAnswer = string.find(data, "%b<>%s+%S+%s+(.+)#(.+)") AddQuestion(curUser,sQuestion,sAnswer) else SendToPlayers("Questions cannot be added by users at this time") end elseif (string.lower(firstWord) == string.lower(ModeTrigg)) then if (bOp) then local _, _, ModeDigit = string.find(data, "%b<>%s+%S+%s+(%d+)") if (ModeDigit ~= nil) then lngMode = tonumber(ModeDigit) if (lngMode == 0) then SendToPlayers("Game is played in chat") elseif (lngMode == 1) then SendToPlayers("Game is played in chat but Questions only show for players") elseif (lngMode == 2) then SendToPlayers("Game is played in PM with Players") else SendToPlayers("Errorous mode, mode set to default") lngMode = lngDefMode end end end elseif (string.lower(firstWord) == string.lower(JoinTrigg)) then if (not (playerArray[curUser])) then playerArray[curUser] = 1 SendToPlayers(curUser.." is now a player!") else SendPMToUser("You are already a player and you didn't know. Possibly because you were autologged in", curUser) end elseif (string.lower(firstWord) == string.lower(PartTrigg)) then if (playerArray[curUser]) then SendToPlayers(curUser.." is no longer a player!") playerArray[curUser] = nil end elseif (string.lower(firstWord) == string.lower(RestartTrigg)) then if (bOp) then SendToPlayers("TriviaBot restarted. All points etc are lost!") SendToPlayers("If Save is used now it will overwrite the old save!") pointArray = {} StopQuiz(1) end elseif ((string.lower(firstWord) == string.lower(PointTrigg)) and (playerArray[curUser])) then if (pointArray[curUser]) then SendToPlayers(curUser.." you have "..pointArray[curUser].." points!") else SendToPlayers(curUser.." you have 0 points!") end elseif ((string.lower(firstWord) == string.lower(HintTrigg)) and (playerArray[curUser])) then if ((lngWord ~= 0) and string.find(strSolved, strCMask, 1, plain)) then if (lngHinted < lngMaxHints) then for nn=1,bcount do local lngRepChar = math.random(1, string.len(strWord)) while 1 do if (boolHintAlwaysHitHidden) then if (string.sub(strSolved, lngRepChar, lngRepChar) == strCMask) then break end else break end lngRepChar = math.random(1, string.len(strWord)) end local strTurn = "" for x=1, string.len(strWord) do if (x == lngRepChar) then strTurn = strTurn..string.sub(strWord, x, x) else strTurn = strTurn..string.sub(strSolved, x, x) end end strSolved = strTurn end lngHinted = lngHinted + 1 lngHintTimeCount = lngHintTimeCount + lngHintTime SendToPlayers("Hint: "..strSolved) acount = "" else SendToPlayers("All hints used for this word!") end else SendToPlayers("No hint needed!") end elseif ((string.lower(firstWord) == string.lower(ScoreTrigg)) and (playerArray[curUser])) then SendPMToUser("SCORES:", curUser) SendPMToUser("---------------------------------------------------------", curUser) for index, value in pointArray do SendPMToUser(index.."'s points: "..value, curUser) end SendPMToUser("---------------------------------------------------------", curUser) elseif ((string.lower(firstWord) == string.lower(PlayerTrigg)) and (playerArray[curUser])) then SendPMToUser("PLAYERS:", curUser) SendPMToUser("---------------------------------------------------------", curUser) for index, value in playerArray do SendPMToUser(index, curUser) end SendPMToUser("---------------------------------------------------------", curUser) elseif (string.lower(firstWord) == string.lower(QuestionsTrigg)) then if (bOp) then SendPMToUser("TOP "..lngMaxShowList.." QUESTIONS:", curUser) SendPMToUser("---------------------------------------------------------", curUser) local lngShowMax = lngMaxShowList for index, value in guessArray do if (lngShowMax == 0) then break end arrTmp = tokenize(guessArray[index], strGSep) strTmp = arrTmp[1] SendPMToUser("["..index.."] "..strTmp, curUser) lngShowMax = lngShowMax - 1 end SendPMToUser("Total # of questions: "..lngGuessmax, curUser) SendPMToUser("---------------------------------------------------------", curUser) end elseif (string.lower(firstWord) == string.lower(StartTrigg)) then if (bOp) then if (lngWord == 0) then playerArray[curUser] = 1 SendGMToAll(BotName, strStart) if (lngMode == 2) then SendToPlayers(strStart) end StopQuiz(1) else SendGMToAll(BotName, "There is already a game going on type "..JoinTrigg.." to join!") end else SendPMToUser("Only Operators can start the Trivia because it might be off for a reason.", curUser) end elseif (string.lower(firstWord) == string.lower(StopTrigg)) then if (bOp) then HoldQuiz() StopQuiz() SendGMToAll(BotName, strStopp) if (lngMode == 2) then SendToPlayers(strStopp) end end elseif (string.lower(firstWord) == string.lower(WordTrigg)) then if ((lngWord ~= 0) and (playerArray[curUser] == "Player")) then SendToPlayers("Current question is: "..strSolved.." ("..string.len(strSolved)..")") end elseif (string.lower(firstWord) == string.lower(QuizTrigg)) then if ((lngWord ~= 0) and (playerArray[curUser])) then SendToPlayers("Question is: "..strQuestion) end elseif (string.lower(firstWord) == string.lower(ReloadTrigg)) then if (bOp) then local _, _, secondWord = string.find(data, "%b<>%s+%S+%s+(%S+)") if not secondWord then secondWord = strQFile end if (secondWord) then handle = io.open(secondWord, "r") if (handle) then handle:close() strTrivFile = secondWord end end HoldQuiz() ReloadQuestions() end else if ((lngWord ~= 0) and (boolPlaydata)) then if (playerArray[curUser]) then local msg = string.sub(data, (4 + string.len(curUser))) if (string.len(msg) >= string.len(strWord)) then local boolDisc = nil local strTurn = "" if string.lower(msg) == string.lower(strWord) then strTurn = strWord boolDisc = 1 else strTurn = strSolved end if (boolDisc) then strSolved = strTurn end if (boolDisc) then if (string.lower(msg) ~= string.lower(strWord)) then SendToOthers(curUser, msg) SendToPlayers("Answer is now: "..strSolved) end end if (string.lower(msg) == string.lower(strWord)) then if (not (pointArray[curUser])) then pointArray[curUser] = 0 end if lngHinted == 3 then pointArray[curUser] = pointArray[curUser] + 1 elseif lngHinted == 2 then pointArray[curUser] = pointArray[curUser] + 2 elseif lngHinted == 1 then pointArray[curUser] = pointArray[curUser] + 3 else pointArray[curUser] = pointArray[curUser] + 4 end SendToPlayers("Answer was: "..strSolved) SendToPlayers("Answer was solved by: "..curUser) SendToPlayers("Next question in "..(lngRestTime * 15).." secs") lngPassed = lngMaxTime SendToPlayers(curUser.."'s points: "..pointArray[curUser]) HoldQuiz() end end end end end end end function HoldQuiz() strWord = "" strQuestion = "" strSolved = "" TimerTicks = lngRestTime * 15 end function StopQuiz(restart) boolRunning = false lngWord = 0 lngPassed = 0 strWord = "" strQuestion = "" strSolved = "" TimerTicks = 0 if (restart) then StartQuiz() end end function StartQuiz() lngWord = math.random(1, lngGuessmax) QWarray = tokenize(guessArray[lngWord], strGSep) strQuestion = QWarray[1] strWord = QWarray[2] strSolved = "" for x=1, string.len(strWord) do if (string.sub(strWord, x, x) == " ") then strSolved = strSolved.." " else strSolved = strSolved..strCMask end end lngPassed = 0 lngHinted = 0 lngHintTimeCount = lngHintTime if string.len(strWord) < 4 then bcount = math.random(1, 2) elseif string.len(strWord) >= 4 and string.len(strWord) <= 9 then bcount = math.random(1, 3) elseif string.len(strWord) >= 9 and string.len(strWord) <= 20 then bcount = math.random(2, 4) else bcount = math.random(3, 5) end boolRunning = true SendToPlayers("Question: "..strQuestion) SendToPlayers("Answer: "..strSolved.." ("..string.len(strSolved)..")") end function VH_OnTimer() if boolRunning then if TimerTicks > 0 then TimerTicks = TimerTicks - 1 return 1 end lngPassed = lngPassed + 1 if (lngPassed == lngMaxTime) then scount = scount + 1 if scount == countSaveTurns then local handle = io.open(strTrivScoreFile, "w") for index, value in pointArray do handle:write(index..strGSep..value.."\r\n") end handle:close() scount = 0 end if (boolRevealAnswer) then SendToPlayers("Answer was '"..strWord.."' and was not solved!") else SendToPlayers("Answer was not solved!") end SendToPlayers("Next question in "..(lngRestTime * 15).." secs") HoldQuiz() elseif (lngPassed >= (lngRestTime + lngMaxTime)) then StopQuiz(1) elseif (lngPassed == lngHintTimeCount) then if (boolAutoHint) then for nn=1,bcount do local lngRepChar = math.random(1, string.len(strWord)) while 1 do if (boolHintAlwaysHitHidden) then if (string.sub(strSolved, lngRepChar, lngRepChar) == strCMask) then break end else break end lngRepChar = math.random(1, string.len(strWord)) end local strTurn = "" for x=1, string.len(strWord) do if (x == lngRepChar) then strTurn = strTurn..string.sub(strWord, x, x) else strTurn = strTurn..string.sub(strSolved, x, x) end end strSolved = strTurn end lngHinted = lngHinted + 1 lngHintTimeCount = lngHintTimeCount + lngHintTime SendToPlayers("Hint: "..strSolved) end end end return 1 end function tokenize (inString,token) _WORDS = {} local matcher = "([^"..token.."]+)" string.gsub(inString, matcher, function (w) table.insert(_WORDS,w) end) return _WORDS end function ReloadQuestions() guessArray = {} lngGuessmax = 0 local boolRestart = nil if (lngWord ~= 0) then SendToPlayers("Reloading questions, game will be shut down!") StopQuiz() boolRestart = 1 end handle = io.open(strTrivFile, "r") if (handle) then local line = handle:read() while line do if ((line ~= "") and (string.find(line, strGSep, 1, plain))) then table.insert(guessArray, line) lngGuessmax = lngGuessmax + 1 end line = handle:read() end handle:close() end if (lngGuessmax < 1) then guessArray = { "The finest car there is?"..strGSep.."Honda CRX del Sol", "Who brought you this TriviaBot?"..strGSep.."Leon a.k.a. Don Leon", "What is the best hub software?"..strGSep.."VerliHub" } lngGuessmax = 3 end if (boolRestart) then SendToPlayers("Reloaded questions, game will be restarted! Current # of questions: "..lngGuessmax) StartQuiz() end strTrivFile = strDefTrivFile end function SaveScores(curUser) local handle = io.open(strTrivScoreFile, "w") for index, value in pointArray do handle:write(index..strGSep..value.."\r\n") end SendGMToUser("Scores saved", curUser) handle:close() end function LoadScores(curUser) local handle = io.open(strTrivScoreFile, "r") if (handle) then local line = handle:read() while line do local arrTmp = tokenize(line, strGSep) if ((arrTmp[1] ~= nil) and (arrTmp[2] ~= nil)) then pointArray[arrTmp[1]] = tonumber(arrTmp[2]) end line = handle:read() end handle:close() if curUser ~= nil then SendGMToUser("Scores Loaded", curUser) end end end function AddQuestion(curUser, sQuestion, sAnswer) local handle = io.open(strQFile, "a") handle:write("Submitted by <"..curUser.."> "..sQuestion..strGSep..sAnswer.."\r\n") SendToPlayers(curUser.." Has added the question "..sQuestion, curUser) SendPMToUser(curUser.." you gave the answer as "..sAnswer, curUser) handle:close() end function SendToPlayers(msg) if ((lngMode > 2) or (lngMode < 0)) then lngMode = lngDefMode end if (lngMode == 0) then SendGMToAll(BotName, msg) elseif (lngMode == 1) then for index, value in playerArray do SendGMToUser(msg, index) end elseif (lngMode == 2) then for index, value in playerArray do SendPMToUser(msg, index) end end end function SendToOthers(msg, curUser) if ((lngMode == 2) and (boolShowGuessesInPM)) then for index, value in playerArray do if (index ~= curUser) then --SendPmToNick(index, BotName, "<"..curUser.."> "..msg) VH:SendDataToUser("<"..curUser.."> "..msg, index) -- Verlihub callback end end end end function SendChatToOthers(chat, curUser) if (lngMode == 2) then for index, value in playerArray do if (index ~= curUser) then --SendToNick(index, "$To: "..index.." From: "..BotName.." $<"..curUser.."> "..chat) VH:SendDataToUser("$To: "..index.." From: "..BotName.." $<"..curUser.."> "..chat, index) -- Verlihub callback end end end end function showHelp(curUser) local bOp = IsOP(curUser) SendGMToUser(" ", curUser) SendGMToUser(" "..BotVersion.." commands:", curUser) SendGMToUser(" ", curUser) if (lngMode == 0) then SendGMToUser(" "..BotVersion.." is set to be played in chat", curUser) elseif (lngMode == 1) then SendGMToUser(" "..BotVersion.." is set to be played in chat (hidden)", curUser) else SendGMToUser(" "..BotVersion.." is set to be played in PM", curUser) end SendGMToUser(" "..HelpTrigg.." - Displays this help", curUser) SendGMToUser(" "..JoinTrigg.." - Joins you to Trivia", curUser) SendGMToUser(" "..PartTrigg.." - Parts you from Trivia", curUser) SendGMToUser(" "..WordTrigg.." - Shows current word (answer)", curUser) SendGMToUser(" "..QuizTrigg.." - Shows current question", curUser) SendGMToUser(" "..PointTrigg.." - Shows your points only", curUser) SendGMToUser(" "..ScoreTrigg.." - Shows everyones score", curUser) SendGMToUser(" "..PlayerTrigg.." - Shows all players in current game", curUser) SendGMToUser(" "..HintTrigg.." - Reveals a char in current word", curUser) SendGMToUser(" "..TopTrigg.." - Shows top ten scores", curUser) SendGMToUser(" "..AddTrigg.." - Adds a question to the Trivia Questions File. Type the question, then separate the answer with a #", curUser) SendGMToUser(" EXAMPLE : tbquestion How fast does a bird fly?#Dont know", curUser) if (bOp) then SendGMToUser(" ", curUser) SendGMToUser(" "..BotVersion.." Operator commands:", curUser) SendGMToUser(" "..StopTrigg.." - Stops the TriviaBot", curUser) SendGMToUser(" "..RestartTrigg.." - Restarts the bot, reseting all scores!", curUser) SendGMToUser(" "..QuestionsTrigg.." - Shows all questions in current game", curUser) SendGMToUser(" "..ReloadTrigg.." () - Reloads questions from "..strDefTrivFile, curUser) SendGMToUser(" "..ModeTrigg.." - To change mode of the game (0-2)!", curUser) SendGMToUser(" "..SaveTrigg.." - Saves scores into "..strTrivScoreFile, curUser) SendGMToUser(" "..LoadTrigg.." - Loads scores from "..strTrivScoreFile, curUser) SendGMToUser(" "..StartTrigg.." - Starts the TriviaBot and automatically adds you as a player", curUser) SendGMToUser(" ", curUser) SendGMToUser(" "..BotVersion.." Modes:", curUser) SendGMToUser(" ".."0 = Game is played in chat!", curUser) SendGMToUser(" ".."1 = Game is played in chat but Questions only show for players", curUser) SendGMToUser(" ".."2 = Game is played in PM with Players", curUser) end end function TopTen(user) local index = {n=0} --table.foreach(pointArray, function(nick, score) table.insert(%index, nick) end) table.foreach(pointArray, function(nick, score) table.insert(index, nick) end) --local func = function(a, b) return %pointArray[a] > %pointArray[b] end local func = function(a, b) return pointArray[a] > pointArray[b] end table.sort(index, func) x = x or 10 if x > index.n then x = index.n end local result = "\r\nTop "..x.." Trivia scores in this hub..\r\n Pos\t Score\t\t Nick\t\r\n" for i = 1, x do result = result.."\r\n "..i..".\t"..pointArray[index[i]].."\t\t"..index[i] end msg = result SendToPlayers(msg) end ---------------[ End functions ]--------------- ---------------[ Global variables ]--------------- BotName = "TriviaBot" ScriptVersion = "v2.2 (for Verlihub)" BotVersion = BotName.." "..ScriptVersion -- This file is not needed it will be created if it not exists strTrivScoreFile = "triviascores.dat" -- This file is needed unless you want to run a Trivia with only three questions ;) strDefTrivFile = "trivia.dat" strTrivFile = strDefTrivFile strQFile = "questions.dat" -- This char will be the global separator? (Make it one that is not used in nicknames nor questions ;) strGSep = "*" -- This char is used to mask the unrevealed chars strCMask = "@" -- Default startup mode of Trivia! lngDefMode = 0 -- (0 = game is played in chat!, 1 = game is played in chat but questions only show for players, 2 = game is played in pm with players) lngMode = lngDefMode -- Max number of questions to show when a Operator ask to see list Questions 0 = 0, -1 = Disabled lngMaxShowList = 5 -- Max no of Hints per word, and always hit on a hidden char lngMaxHints = 4 boolHintAlwaysHitHidden = nil -- (nil = off, 1 = on) -- How much time before a question end. Time in Trivia is measured in 15 seconds, 1 = 15 seconds, 2 = 30 seconds etc! -- Rest is how long to rest after a question is solved depening on when the word is solved in the Timer loop it is ±15 secs to the setting! lngMaxTime = 24 -- (Needs to be a multiple of 4 i.e. 4 / 8 / 12 / 16) lngRestTime = 1 -- This will cause to wait 15 secs between questions. lngHintTime = lngMaxTime / 4 -- Gives a hint in 6 secs. -- How often should the bot autosave the scores boolAutoSaveScores = 1 -- (nil = off, 1 = on) countSaveTurns = 10 -- After how many questions should the scores be saved -- Should bot reveal the correct answer once time is up? boolRevealAnswer = 1 -- (nil = off, 1 = on) -- Should the bot display what guess made the word become a little clearer if played in PM! boolShowGuessesInPM = 1 -- (nil = off, 1 = on) -- Should the bot Auto Reveal Hints? boolAutoHint = 1 -- (nil = off, 1 = on) -- Should the bot auto login every user on connect ? boolAutoLogin = nil -- (nil = off, 1 = on) -- Do you want users to be able to add questions ? Operators can always add questions. boolAddQuestion = 1 -- (nil = No, 1 = Yes) -- TriggStart is what char the commands should begin with I use ! but you can use # or whatever TriggStart = "#" -- Theese are the different triggers I've tried to name them so that one easily understand what they do! -- I include TriggStart and then the word within "" this word is not casesensitive! JoinTrigg = TriggStart.."tbjoin" PartTrigg = TriggStart.."tbpart" StartTrigg = TriggStart.."tbstart" StopTrigg = TriggStart.."tbstop" WordTrigg = TriggStart.."tbword" QuizTrigg = TriggStart.."tbquestion" RestartTrigg = TriggStart.."tbrestart" PointTrigg = TriggStart.."tbpoints" ScoreTrigg = TriggStart.."tbscores" PlayerTrigg = TriggStart.."tbplayers" QuestionsTrigg = TriggStart.."tbquestions" ReloadTrigg = TriggStart.."tbreload" HelpTrigg = TriggStart.."tbhelp" ExtraHelpTrigg = TriggStart.."tbhelp+" SaveTrigg = TriggStart.."tbsavescores" LoadTrigg = TriggStart.."tbloadscores" ModeTrigg = TriggStart.."tbmode" HintTrigg = TriggStart.."tbhintme" TopTrigg = TriggStart.."tbtopten" AddTrigg = TriggStart.."tbaddquestion" boolRunning = false TimerTicks = 0 bcount = 0 scount = 0 sQuestion = "" sAnswer = "" guessArray = {} lngGuessmax = 0 playerArray = {} pointArray = {} lngWord = 0 lngPassed = 0 lngHinted = 0 strWord = "" strQuestion = "" strSolved = "" strBanner = "Type '"..StartTrigg.."' to start Trivia, '"..StopTrigg.."' to stop Trivia, type '"..JoinTrigg.."' to Join and '"..PartTrigg.."' to Part!" strStart = "TriviaBot "..ScriptVersion.." started! "..strBanner strStopp = "TriviaBot "..ScriptVersion.." stopped! "..strBanner ---------------[ End global variables ]--------------- ---------------[ Script entry point ]--------------- res, err = VH:AddRobot(BotName, 10, "Trivia Bot (QUIZ)", "Hub\7", "admin@email.com", "0") ReloadQuestions() LoadScores() ---------------[ End script entry point ]---------------