106 lines
2.8 KiB
Lua
106 lines
2.8 KiB
Lua
local UserInputService = game:GetService("UserInputService")
|
|
|
|
local join = require(script.Parent.join)
|
|
|
|
--[[
|
|
CLILUACORE-318: Revisit this approach and evaluate if it adequately
|
|
addresses existing or future scams
|
|
]]
|
|
local ClickScamDetector = {}
|
|
ClickScamDetector.__index = ClickScamDetector
|
|
|
|
--[[
|
|
Create a new scam detector with the specified options
|
|
|
|
This object will track any clicks or confirm button presses that occur
|
|
during its lifetime and attempt to determine whether the player is
|
|
being asked to spam clicks or button presses. When processing an action,
|
|
users of this object can abort their behavior if calling isClickValid
|
|
returns false.
|
|
]]
|
|
function ClickScamDetector.new(options)
|
|
--[[
|
|
Overwrite default values with provided options
|
|
]]
|
|
options = join({
|
|
-- The number of clicks within the window that is interpreted as a scam
|
|
clickSpeedThreshold = 3,
|
|
-- The window in which to measure clicks
|
|
clickTimeWindow = 1,
|
|
-- A delay to allow clicks to stack up and prevent immediate clicks
|
|
initialDelay = 1,
|
|
-- Allow a button input to be associated with this click detection
|
|
buttonInput = nil,
|
|
}, options)
|
|
|
|
local self = {
|
|
_inputConnection = nil,
|
|
|
|
_clickCount = 0,
|
|
_startTime = tick(),
|
|
_options = options,
|
|
}
|
|
|
|
setmetatable(self, ClickScamDetector)
|
|
|
|
self._inputConnection = UserInputService.InputBegan:Connect(function(input)
|
|
self:_onInput(input)
|
|
end)
|
|
|
|
return self
|
|
end
|
|
|
|
--[[
|
|
Track mouse inputs, counting the number that occurred in the last
|
|
CLICK_TIME_WINDOW duration
|
|
|
|
Includes touch inputs and pressing the A button on a gamepad
|
|
]]
|
|
function ClickScamDetector:_onInput(input)
|
|
local inputType = input.UserInputType
|
|
|
|
local isGamepad = self._options.buttonInput ~= nil and input.KeyCode == self._options.buttonInput
|
|
|
|
local isMouseOrTouch = inputType == Enum.UserInputType.MouseButton1
|
|
or inputType == Enum.UserInputType.Touch
|
|
|
|
if isGamepad or isMouseOrTouch then
|
|
self._clickCount = self._clickCount + 1
|
|
|
|
delay(self._options.clickTimeWindow, function()
|
|
self._clickCount = self._clickCount - 1
|
|
end)
|
|
end
|
|
end
|
|
|
|
--[[
|
|
Determine whether or not there's a possibility of a scam occurring
|
|
and return whether or not we believe the click to be valid
|
|
]]
|
|
function ClickScamDetector:isClickValid()
|
|
--[[
|
|
If the mouse behavior is locked by dev-facing APIs, clicks are not valid
|
|
]]
|
|
if UserInputService.MouseBehavior == Enum.MouseBehavior.LockCurrentPosition then
|
|
return false
|
|
end
|
|
|
|
--[[
|
|
Don't allow any clicks until the initial delay has passed;
|
|
]]
|
|
if tick() - self._startTime < self._options.initialDelay then
|
|
return false
|
|
end
|
|
|
|
return self._clickCount / self._options.clickTimeWindow < self._options.clickSpeedThreshold
|
|
end
|
|
|
|
--[[
|
|
Cleanup connection to InputService; should be called when
|
|
the UI element using this object is destroyed
|
|
]]
|
|
function ClickScamDetector:destroy()
|
|
self._inputConnection:Disconnect()
|
|
end
|
|
|
|
return ClickScamDetector |