Anti-Abuse
Rewind includes multiple layers of protection against cheating and abuse.
Built-in Protections
1. Duplicate Detection
Prevents players from registering multiple hits with the same shot:
-- Same attack can't hit the same target twice
-- Tracked by: attackerId + targetId + timestamp + weaponId + mode
For melee weapons, duplicate detection is weapon-aware:
- Same melee swing can hit multiple targets
- But can't hit the same target twice per swing
2. Distance Sanity Checks
Validates that the claimed origin is reasonable:
-- Server checks:
-- distance(claimedOrigin, actualPosition) < maxAllowedDrift
Rewind.Start({
maxRayDistance = 1000,
})
If the claimed shot origin is too far from where the player actually is, the hit is rejected with RejectReason.InvalidOrigin.
3. Timestamp Validation
Ensures timestamps are within acceptable bounds:
-- Checks:
-- 1. Timestamp is not in the future
-- 2. Timestamp is not too far in the past
-- 3. Timestamp is within rewind window
Rewind.Start({
maxRewindMs = 1000, -- Maximum 1 second rewind
})
4. Rate Limiting
Prevents spam attacks:
local RateLimiter = require(Rewind.Util.RateLimiter)
local limiter = RateLimiter.new({
maxRequests = 20, -- Max requests
windowSize = 1.0, -- Per second
})
-- In hit handler
if not limiter:check(player) then
return -- Rate limited
end
Rejection Reasons
When a hit is rejected, you get a specific reason:
local result = Rewind.Validate(player, "Ray", params)
if not result.hit then
print("Rejected:", result.reason)
-- Possible reasons:
-- "rate_limited" - Player firing too fast
-- "duplicate_shot" - Already processed this shotId
-- "no_hit" - No target was hit
-- "no_humanoid" - Hit target has no humanoid
-- "friendly_fire" - Target is on same team
-- "not_server" - Called from client
-- "out_of_range" - Target beyond weapon range
-- "invalid_params" - Invalid validation parameters
-- "vehicle_blocked" - Vehicle absorbed the hit
-- "armor_absorbed" - Armor fully absorbed damage
end
RejectReason Enum
type RejectReason =
| "rate_limited"
| "duplicate_shot"
| "no_hit"
| "no_humanoid"
| "friendly_fire"
| "not_server"
| "out_of_range"
| "invalid_params"
| "vehicle_blocked"
| "armor_absorbed"
| nil -- nil = success/hit
Custom Validation
Add your own validation logic:
local function customValidate(player, params)
-- Check weapon ownership
if not playerOwnsWeapon(player, params.weaponId) then
return false, "WeaponNotOwned"
end
-- Check ammo
if not hasAmmo(player, params.weaponId) then
return false, "NoAmmo"
end
-- Check if weapon is on cooldown
if isOnCooldown(player, params.weaponId) then
return false, "OnCooldown"
end
return true
end
-- Apply before Rewind validation
HitRemote.OnServerEvent:Connect(function(player, hitData)
local valid, reason = customValidate(player, hitData)
if not valid then
warn("Custom validation failed:", reason)
return
end
-- Continue with Rewind validation
local result = Rewind.Validate(player, "Ray", hitData)
-- ...
end)
Logging Suspicious Activity
Track potentially abusive behavior:
local suspiciousPlayers = {}
local function trackSuspicious(player, reason)
if not suspiciousPlayers[player] then
suspiciousPlayers[player] = {}
end
table.insert(suspiciousPlayers[player], {
time = os.time(),
reason = reason,
})
-- Flag if too many suspicious actions
if #suspiciousPlayers[player] > 10 then
flagForReview(player)
end
end
-- In validation
local result = Rewind.Validate(player, "Ray", params)
if not result.hit then
if result.reason == "out_of_range" or
result.reason == "rate_limited" then
trackSuspicious(player, result.reason)
end
end
Best Practices
-
Never trust client data - Always validate everything server-side
-
Log rejections - Track patterns that might indicate cheating
-
Use rate limiting - Prevent spam even if hits are rejected
-
Layer defenses - Combine Rewind with your own checks
-
Test edge cases - High ping, packet loss, rapid movement
-- Example: Full validation pipeline
local function handleHit(player, hitData)
-- Layer 1: Rate limiting
if not rateLimiter:check(player) then
return { accepted = false, reason = "RateLimited" }
end
-- Layer 2: Basic sanity
if not hitData or type(hitData) ~= "table" then
return { accepted = false, reason = "InvalidData" }
end
-- Layer 3: Custom validation
local valid, reason = customValidate(player, hitData)
if not valid then
return { accepted = false, reason = reason }
end
-- Layer 4: Rewind validation
return Rewind.Validate(player, hitData.mode, hitData)
end