local config = {
allowCheckFromDistance = 5, -- allow the use of talkaction in range of x sqm's
allowedDailyCheckInvocations = 5, -- allow the talkaction to be invoked x times daily by a player
allowedDailyCheckInpections = 5, -- amount of times a player can be checked
allowToPerfomCheckOnStorageValue = 1, -- allow to perfom the talkaction on players with storages.isCheckable set to x value
executePenaltyAfter = 3, -- trigger the penalty after 3 unsuccessful checks
warningInterval = 1 * 60, -- send warning message every minute
answerDuration = 5 * 60, -- 5 minutes
checkDelay = 60 * 60, -- 60 minutes
storages = {
isCheckable = 80500,
dailyCheckInvocation = 80501, -- amount of times the player has invoked the talkaction
dailyCheckInspection = 80502, -- amount of times a player has been checked
failedCheckCount = 80503, -- amount of unsuccessful checks
lastCheck = 80504, -- time when the players was last checked
},
questions = {
-- "question" = "answer",
["What is 2+2?"] = 4,
["What is 5 x 3?"] = 15,
},
messages = {
onWarningMessage = "You have %s minutes left to answer the question.",
onCorrectAnswer = "Congratulations! You\'ve successfullly answered the question.",
onWrongAnswer = "You\'ve provided a wrong answer to the question. You have received a warning.",
onTooManyChecks = "This player has been checked too many times today and cannot be checked again until tomorrow.",
onAnswerTimeOut = "You\'ve not answered the question within allowed time limit. You have received a warning.",
onDailyLimitReached = "You have reached your daily limit of checks and cannot check any more players today.",
onCheckCount = "You currently have %s/%s warnings.",
onHelpCheckBot = "Type !checkbot NICK to check if a player is a bot. They must answer a question within 5 minutes.",
onHelpNoAfk = "Type !noafk RESPONSE to prove that you\'re not a botter.",
onCheckInitiated = "You have initiated a check on %s. They must respond within %s minutes.",
onPlayerRecentlyChecked = "This player was checked recently and cannot be checked again yet.",
onPlayerPendingCheck = "This player is currently undergoing a check. Please try again later.",
onPlayerNotCheckable = "This player is not checkable.",
onPlayerNotFound = "Player %s is either offline or doesn\'t exist.",
onPlayerTooFar = "This player is too far away from you. You need to be within %s sqm\'s to invoke this command.",
onPlayerInspection = "%s has performed a check on you. Answer the following question: %s within %s minutes. Respond with !noafk RESPONSE.",
onFailToProvideAnswer = "%s has either exceeded the time limit or responded wrongly to the question. He has been penalized.",
onSuccessToProvideAnswer = "%s has successfully answered the question.",
onPenalty = "You have failed at the BotCheck. You will now receive a penalty.",
onNotUndergoingCheck = "You\'re not being checked."
},
}
local BotCheck = {}
BotCheck.questions = {}
BotCheck.pendingChecks = {}
BotCheck.formatTime = function(sec)
local minutes = math.floor(sec / 60)
local seconds = sec % 60
return string.format("%02d:%02d", minutes, seconds)
end
BotCheck.drawRandomQuestion = function()
if #BotCheck.questions == 0 then
for question in pairs(config.questions) do
table.insert(BotCheck.questions, question)
end
end
local randomIndex = math.random(#BotCheck.questions)
local question = BotCheck.questions[randomIndex]
local answer = config.questions[question]
return question, answer
end
BotCheck.perfomCheck = function(playerGuid, targetPlayerGuid)
local player = Player(playerGuid)
local targetPlayer = Player(targetPlayerGuid)
if not player or not targetPlayer then return end
-- Get random question
local question, answer = BotCheck.drawRandomQuestion()
-- Get answer duration
local answerDuration = BotCheck.formatTime(config.answerDuration)
-- Send message to the target player
local targetPlayerMessage = string.format(config.messages.onPlayerInspection, player:getName(), question, answerDuration)
targetPlayer:sendTextMessage(MESSAGE_STATUS_CONSOLE_RED, targetPlayerMessage)
-- Send message to the player
local playerMessage = string.format(config.messages.onCheckInitiated, targetPlayer:getName(), answerDuration)
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_RED, playerMessage)
-- Add a record to pending checks
BotCheck.pendingChecks[targetPlayerGuid] = {
inspectorGuid = playerGuid,
answer = answer,
events = {}
}
-- Schedule initial warning message
BotCheck.sendWarningMessage(playerGuid, targetPlayerGuid, config.answerDuration / config.warningInterval)
-- Update checked player storages
targetPlayer:setStorageValue(config.storages.dailyCheckInspection, targetPlayer:getStorageValue(config.storages.dailyCheckInspection) + 1)
targetPlayer:setStorageValue(config.storages.lastCheck, os.time() + config.checkDelay)
-- Update storages of player who performs the check
player:setStorageValue(config.storages.dailyCheckInvocation, player:getStorageValue(config.storages.dailyCheckInvocation) + 1)
end
BotCheck.sendWarningMessage = function(playerGuid, targetPlayerGuid, minutesLeft)
local targetPlayer = Player(targetPlayerGuid)
if not targetPlayer then return end
if minutesLeft > 0 then
local message = string.format(config.messages.onWarningMessage, BotCheck.formatTime(minutesLeft * 60))
targetPlayer:sendTextMessage(MESSAGE_STATUS_CONSOLE_RED, message)
-- Schedule next warning
local nextWarningTime = config.warningInterval * 1000
minutesLeft = minutesLeft - (config.warningInterval / 60)
BotCheck.pendingChecks[targetPlayerGuid].events["warningEvent"] = addEvent(BotCheck.sendWarningMessage, nextWarningTime, playerGuid, targetPlayerGuid, minutesLeft)
else
targetPlayer:sendTextMessage(MESSAGE_STATUS_CONSOLE_RED, config.messages.onAnswerTimeOut)
targetPlayer:setStorageValue(config.storages.failedCheckCount, targetPlayer:getStorageValue(config.storages.failedCheckCount) + 1)
local failedChecks = targetPlayer:getStorageValue(config.storages.failedCheckCount)
local maxFailedChecks = config.executePenaltyAfter
if failedChecks == maxFailedChecks then
targetPlayer:sendTextMessage(MESSAGE_STATUS_CONSOLE_RED, config.messages.onPenalty)
BotCheck.stopEventsAndRemovePendingRecord(targetPlayerGuid)
BotCheck.applyPenalty(targetPlayerGuid)
local player = Player(playerGuid)
if not player then return end
local playerMessage = string.format(config.messages.onFailToProvideAnswer, targetPlayer:getName())
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_RED, playerMessage)
else
local message = string.format(config.messages.onCheckCount, failedChecks, maxFailedChecks)
targetPlayer:sendTextMessage(MESSAGE_STATUS_CONSOLE_RED, message)
local player = Player(playerGuid)
if not player then return end
local playerMessage = string.format(config.messages.onFailToProvideAnswer, targetPlayer:getName())
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_RED, playerMessage)
BotCheck.stopEventsAndRemovePendingRecord(targetPlayerGuid)
end
end
end
BotCheck.stopEventsAndRemovePendingRecord = function(playerGuid)
if BotCheck.pendingChecks[playerGuid] then
for _, eventId in pairs(BotCheck.pendingChecks[playerGuid].events) do
stopEvent(eventId)
end
BotCheck.pendingChecks[playerGuid] = nil
end
end
BotCheck.applyPenalty = function(targetPlayerGuid)
print("applying penalty to playerGuid: " .. targetPlayerGuid)
end
BotCheck.onCheckAnswer = function(targetPlayerGuid, playerAnswer)
local targetPlayer = Player(targetPlayerGuid)
if not targetPlayer then return end
if not BotCheck.pendingChecks[targetPlayerGuid] then
targetPlayer:sendCancelMessage(config.messages.onNotUndergoingCheck)
return false
else
local data = BotCheck.pendingChecks[targetPlayerGuid]
local expectedAnswer = data.answer
local player = Player(data.inspectorGuid)
if tostring(expectedAnswer):trim():lower() == tostring(playerAnswer):trim():lower() then
targetPlayer:sendTextMessage(MESSAGE_STATUS_CONSOLE_RED, config.messages.onCorrectAnswer)
if player then
local message = string.format(config.messages.onSuccessToProvideAnswer, targetPlayer:getName())
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_RED, message)
end
BotCheck.stopEventsAndRemovePendingRecord(targetPlayerGuid)
else
targetPlayer:setStorageValue(config.storages.failedCheckCount, targetPlayer:getStorageValue(config.storages.failedCheckCount) + 1)
local failedChecks = targetPlayer:getStorageValue(config.storages.failedCheckCount)
local maxFailedChecks = config.executePenaltyAfter
if failedChecks == maxFailedChecks then
targetPlayer:sendTextMessage(MESSAGE_STATUS_CONSOLE_RED, config.messages.onPenalty)
BotCheck.applyPenalty(targetPlayerGuid)
else
targetPlayer:sendTextMessage(MESSAGE_STATUS_CONSOLE_RED, config.messages.onWrongAnswer)
local message = string.format(config.messages.onCheckCount, failedChecks, maxFailedChecks)
targetPlayer:sendTextMessage(MESSAGE_STATUS_CONSOLE_RED, message)
end
if player then
local message = string.format(config.messages.onFailToProvideAnswer, targetPlayer:getName())
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_RED, message)
end
BotCheck.stopEventsAndRemovePendingRecord(targetPlayerGuid)
end
end
end
local weirdBotCheck = TalkAction("!checkbot")
function weirdBotCheck.onSay(player, words, param, type)
if not param or param == "" then
player:sendCancelMessage(config.messages.onHelpCheckBot)
return false
end
-- Check whether the player who invokes the talkaction has any invocations left
local dailyCheckInvocations = player:getStorageValue(config.storages.dailyCheckInvocation)
if dailyCheckInvocations >= config.allowedDailyCheckInvocations then
player:sendCancelMessage(config.messages.onDailyLimitReached)
return false
end
-- Check if the target player exists
local targetPlayer = Player(param)
if not targetPlayer then
local message = string.format(config.messages.onPlayerNotFound, param)
player:sendCancelMessage(message)
return false
end
-- Check whether the player is checkable
local isTargetPlayerCheckable = targetPlayer:getStorageValue(config.storages.isCheckable) == config.allowToPerfomCheckOnStorageValue
if isTargetPlayerCheckable then
local targetPlayerGuid = targetPlayer:getGuid()
local playerGuid = player:getGuid()
-- Check whether the player is within allowed range
if targetPlayer:getPosition():getDistance(player:getPosition()) <= config.allowCheckFromDistance then
local lastCheckTime = targetPlayer:getStorageValue(config.storages.lastCheck)
-- Check whether the target player hasn't been check in the previous hour
if lastCheckTime > os.time() then
player:sendCancelMessage(config.messages.onPlayerRecentlyChecked)
return false
-- Check whether the player doesn't have a pending check
elseif BotCheck.pendingChecks[targetPlayerGuid] then
player:sendCancelMessage(config.messages.onPlayerPendingCheck)
return false
-- Check whether the player hasn't reached the limit of daily checks
elseif targetPlayer:getStorageValue(config.storages.dailyCheckInspection) >= config.allowedDailyCheckInpections then
player:sendCancelMessage(config.messages.onTooManyChecks)
return false
-- All conditions are satisfied, allow the player to perform a check on target player
else
BotCheck.perfomCheck(playerGuid, targetPlayerGuid)
end
-- Player is not within the allowed range
else
local message = string.format(config.messages.onPlayerTooFar, config.allowCheckFromDistance)
player:sendCancelMessage(message)
return false
end
else
player:sendCancelMessage(config.messages.onPlayerNotCheckable)
return false
end
return false
end
weirdBotCheck:separator(" ")
weirdBotCheck:register()
local talkAction = TalkAction("!noafk")
function talkAction.onSay(player, words, param, type)
if not param or param == "" then
player:sendCancelMessage(config.messages.onHelpNoAfk)
return false
end
local playerGuid = player:getGuid()
BotCheck.onCheckAnswer(playerGuid, param)
return false
end
talkAction:separator(" ")
talkAction:register()
local globalEvent = GlobalEvent("BotCheckStartup")
function globalEvent.onStartup()
local storagesToReset = {config.storages.dailyCheckInspection, config.storages.dailyCheckInvocation, config.storages.failedCheckCount}
for _, storage in ipairs(storagesToReset) do
local sql = "UPDATE player_storage SET `value` = 0 WHERE `key` = " .. storage
db.query(sql)
end
return true
end
globalEvent:register()