| |
|
| |
|
| | local Players = game:GetService("Players")
|
| | local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
| |
|
| |
|
| | local rateLimits = {}
|
| |
|
| | local MAX_EVENTS_PER_SECOND = 10
|
| | local RATE_WINDOW = 1
|
| |
|
| | local function getRateData(player, eventName)
|
| | if not rateLimits[player.UserId] then
|
| | rateLimits[player.UserId] = {}
|
| | end
|
| | if not rateLimits[player.UserId][eventName] then
|
| | rateLimits[player.UserId][eventName] = {
|
| | count = 0,
|
| | lastReset = tick(),
|
| | }
|
| | end
|
| | return rateLimits[player.UserId][eventName]
|
| | end
|
| |
|
| |
|
| | _G.IsRateLimited = function(player, eventName)
|
| | local data = getRateData(player, eventName)
|
| | local now = tick()
|
| |
|
| | if now - data.lastReset > RATE_WINDOW then
|
| | data.count = 0
|
| | data.lastReset = now
|
| | end
|
| |
|
| | data.count = data.count + 1
|
| |
|
| | if data.count > MAX_EVENTS_PER_SECOND then
|
| | warn("Rate limit exceeded for " .. player.Name .. " on event " .. eventName)
|
| | return true
|
| | end
|
| |
|
| | return false
|
| | end
|
| |
|
| |
|
| | _G.ValidateDistance = function(player, targetPosition, maxDistance)
|
| | local character = player.Character
|
| | if not character then return false end
|
| |
|
| | local rootPart = character:FindFirstChild("HumanoidRootPart")
|
| | if not rootPart then return false end
|
| |
|
| | local distance = (rootPart.Position - targetPosition).Magnitude
|
| | return distance <= maxDistance
|
| | end
|
| |
|
| |
|
| | _G.ValidateCash = function(player, amount)
|
| | local leaderstats = player:FindFirstChild("leaderstats")
|
| | if not leaderstats then return false end
|
| |
|
| | local cash = leaderstats:FindFirstChild("Cash")
|
| | if not cash then return false end
|
| |
|
| | return cash.Value >= amount
|
| | end
|
| |
|
| |
|
| | Players.PlayerRemoving:Connect(function(player)
|
| | rateLimits[player.UserId] = nil
|
| | end)
|
| |
|
| | print("Anti-Cheat Manager initialized")
|
| |
|