So I own an obbying franchise that includes a game inspired off “Eternal Towers of Hell (EToH)” (formerly, “Kiddie’s Tower of Hell”, (KToH), “Juke’s Tower of Hell” (JToH), & “Jupiter’s Tower of Hell” (JToH). Whenever a player is in a tower obby, the script detects when the player reaches certain checkpoints of the tower to prevent them from cheating by skipping the tower, however instead of using physical parts with .Touched:Connect() functions, I use intervals to detect if the player’s in the volume, but it causes some lag spikes. How can I reduce the lag?
When you say “intervals” it sounds like you run position checks on the server to determine which zone each player is in. This doesn’t require any network communication so I don’t see how this alone would cause lag spikes (unless your detection code is so extremely inefficient that it slows down the server itself. At that point it’s not lag spikes but just constant lag).
What exactly do you use to detect when a player has reached a checkpoint? What happens afterwards? If your checkpoints have some sort of visual effects that are handled by the server then those effects have to be replicated to the rest of the clients which takes up bandwidth. This can be easily fixed by only making the clients render the effects.
It’s not possible to tell what’s causing your lag spikes with the information given.
It seems like you’re checking per player x per checkpoint, which I believe would cause slight performance issues but I’m not sure based of what you provided. If you are doing it this way then I think instead of looping through all checkpoints and all players, check only the player’s position against the next checkpoint, not all checkpoints.
Either way you have it, for stuff like this I highly suggest you use for _, player in ipairs() to iterate through players one by one instead of all at once
Basically I set checkpoints in the tower to prevent anti cheat. For example, let’s say a tower has 3 checkpoints, and a player touches the win pad without getting all of them, or they did it out of order, then they’ll their run won’t be counted and they’ll be automatically temporarily banned. I also have walkspeed & jump power checks in some checkpoints, that will also detect cheating. I have a module in “ServerStorage” where I store each tower’s anti cheat settings like its’ checkpoints and each checkpoints configuration (Position, Size, Orientation, MinimumTime, etc.)
ok so lag spikes usually happen in this pattern when you constantly scan every player against every checkpoint volume on an interval (xample, while task.wait() loops). That becomes whole lotta perforamance cost and creates bursts of work on the server ![]()
well its a good thing that i have encountered this type of thing when making an obby and here are my solutions myself!
first i use spatial queries instead of manually checking if a player is inside a box every interval! heres an example
local OverlapParams = OverlapParams.new()
OverlapParams.FilterType = Enum.RaycastFilterType.Include
OverlapParams.FilterDescendantsInstances = {workspace.PlayersFolder}
local function checkCheckpoint(checkpoint)
local parts = workspace:GetPartBoundsInBox(checkpoint.CFrame, checkpoint.Size, OverlapParams)
for _, part in ipairs(parts) do
local character = part:FindFirstAncestorOfClass("Model")
if character then
local player = game.Players:GetPlayerFromCharacter(character)
if player then
processCheckpoint(player, checkpoint)
end
end
end
end
and then only check when a player is close to a checkpoint
local cp = playerData[player].currentCheckpoint
checkCheckpoint(checkpoints[cp])
checkCheckpoint(checkpoints[cp + 1])
and also lower your check frequency to be something like 0.05 or smthin you like
while task.wait(0.05) do
hope that makes sense because im just guessing what your game looks like (since you didnt show what your scripts look like)
![]()
Have you used the microprofiler to see if a script is causing server slowdown yet?
Have you checked if you’re creating a lot of objects at once?
Have you re-visited your remote events about unbounded tables being sent to clients?
We’re really playing a guessing game, if you could provide a little more information it would help a lot!
Yes, the lag spikes are consistently during when the player is in a tower run, which is when the checkpoints start their interval.
This actually is a really good idea, however I’m having trouble incorporating it with my current script. Is it okay if you can help me incorporate it into my script?
@SyntaxMenace @qushenya would it be easier if I messaged you the code blocks that handle the checkpoints?
Here’s the game link Planets of Hell | Play on Roblox
yea sure thing! just show your script and ill see what i can do
Alright, thank you so much
I’ll send you what I wrote for the anti-cheat checkpoints. These are snippets of the game’s full server script.
Here I declare the variable for the AntiCheat module which contains each tower’s checkpoints.
local Security = require(ServerStorage.AntiCheat)
Here are the values tracking the progression of the anti-cheat:
AntiCheatProgress = 0,
LastAntiCheatCheckpointElapsed = nil,
CheatFlag = false,
CheatReason = nil,
This determines the win’s anti-cheat:
function validateAntiCheat(player, tower, elapsed, mode, rs)
if rs and rs.AdminAntiCheatBypass == true then
return true, nil
end
local sec = Security:Get(tower.Abbr)
if not sec then return true, nil end
if sec.AntiCheat and #sec.AntiCheat > 0 then
local expected = 0
for _, entry in ipairs(sec.AntiCheat) do
if entry.Type == "Checkpoint" then
expected = math.max(expected, tonumber(entry.Index) or 0)
end
end
if expected > 0 and (tonumber(rs.AntiCheatProgress) or 0) < expected then
return false, "Missing checkpoints"
end
end
if mode == "Legit" then
local min = tonumber(sec.MinimumLegitTime)
if min and (elapsed + 0.15) < min then
return false, "Legit minimum time"
end
elseif mode == "AllJump" then
local min = tonumber(sec.MinimumAllJumpTime)
if min and (elapsed + 0.15) < min then
return false, "AllJump minimum time"
end
end
if rs.CheatFlag then
return false, rs.CheatReason or "CheatFlag"
end
return true, nil
end
Win check:
local ok, reason = validateAntiCheat(player, tower, elapsed, rs.Mode, rs)
if not ok then
local antiReason = reason or "AntiCheat"
if tower and not string.find(antiReason, "Tower:", 1, true) then
antiReason = antiReason .. " | Tower: " .. tostring(tower.Abbr or tower.FullName or "Unknown")
end
punishAntiCheat(player, difficultyIndex(tower.Difficulty), antiReason, tower)
consumeLoadedRunSlot(player, rs)
endRun(player, "Cheat")
return
end
Here’s the part that actually detects when they player is in the checkpoints (the thing I mentioned earlier causing lag spikes)
if rs.InRun and rs.Tower and rs.Mode ~= "Practice" and rs.AdminAntiCheatBypass ~= true then
if now >= (rs.NextAntiCheatTick or 0) then
local compiledAnti = getCompiledAntiCheatForTower(rs.Tower)
if compiledAnti and compiledAnti.Entries and #compiledAnti.Entries > 0 then
local char = player.Character
local root = getCharacterRootPart(char)
local rootCFrame = root and root.CFrame or nil
local rootPosition = rootCFrame and rootCFrame.Position or nil
local hum = char and char:FindFirstChildOfClass("Humanoid")
local previousRootPosition = rs.LastAntiCheatRootPosition
local shouldScanAntiCheat = rootPosition ~= nil and hum ~= nil and hum.Health > 0
if shouldScanAntiCheat then
local candidateEntries = getRunAntiCheatCandidates(rs, compiledAnti, rootPosition, now)
if candidateEntries and #candidateEntries > 0 then
for _, compiledEntry in ipairs(candidateEntries) do
local entry = compiledEntry.Entry
if antiCheatCharacterTouchesOrCrosses(entry, rootCFrame, previousRootPosition, hum) then
if compiledEntry.HasMovementRules and now >= (rs.SpeedIgnoreUntil or 0) and antiCheatMovementViolation(entry, hum) then
rs.CheatFlag = true
end
if compiledEntry.Type == "Checkpoint" then
local idx = tonumber(compiledEntry.Index) or tonumber(entry.Index) or 0
local expected = rs.AntiCheatProgress + 1
if idx == expected then
local minTime = tonumber(entry.MinimumTime) or 0
local requiredFromPrevious = tonumber(entry.RequiredTimeFromPreviousCheckpoint) or tonumber(entry.MinimumTimeFromPreviousCheckpoint) or 0
local elapsed = now - rs.StartTime
local lastElapsed = tonumber(rs.LastAntiCheatCheckpointElapsed)
local sincePrevious = lastElapsed and math.max(0, elapsed - lastElapsed) or elapsed
if minTime > 0 and elapsed < minTime then
rs.CheatFlag = true
elseif requiredFromPrevious > 0 and sincePrevious < requiredFromPrevious then
rs.CheatFlag = true
else
rs.AntiCheatProgress = idx
rs.LastAntiCheatCheckpointElapsed = elapsed
end
elseif idx > expected then
rs.CheatFlag = true
end
elseif compiledEntry.Type == "Deactivator" then
local clearFromIndex = math.floor(tonumber(compiledEntry.ClearFromIndex) or tonumber(entry.StartIndex) or tonumber(entry.FromIndex) or 1)
if clearFromIndex < 1 then clearFromIndex = 1 end
local currentProgress = math.max(0, tonumber(rs.AntiCheatProgress) or 0)
if currentProgress >= clearFromIndex then
rs.AntiCheatProgress = clearFromIndex - 1
rs.LastAntiCheatCheckpointElapsed = nil
end
end
end
end
end
rs.LastAntiCheatRootPosition = rootPosition
end
end
end
end
Helper functions:
local function getAntiCheatDefinitionForTower(tower)
local anti = Security:Get(abbr)
-- didnt put entire function, I don't think you need it, but if you do I'll send it
end
local function getCompiledAntiCheatForTower(tower)
local function getRunAntiCheatCandidates(rs, compiled, rootPosition, now)
antiCheatCharacterTouchesOrCrosses = function(entry, rootCFrame, previousRootPosition, humanoid)
yea so youre polling volumes for every player repeatedly and doing a lot of math each tick, thats what causes the lag ![]()
anyways heres what i found in your scirpts that needs fixing:
- lower the scan frequency! this is too much checking
if now >= (rs.NextAntiCheatTick or 0) then
so make rs.nextanticheat to something like this
rs.NextAntiCheatTick = now + 0.15
- only check the next checkpoint, so instead of chekciing every entry
for _, compiledEntry in ipairs(candidateEntries) do
you should js check
local nextIndex = rs.AntiCheatProgress + 1
local entry = compiled.Checkpoints[nextIndex]
ok heres an example
compiled = {
Checkpoints = {
[1] = {...},
[2] = {...},
[3] = {...}
}
}
then you just do this:
local entry = compiled.Checkpoints[rs.AntiCheatProgress + 1]
if entry and antiCheatCharacterTouchesOrCrosses(entry, rootCFrame, previousRootPosition, hum) then
- Before doin expensiv volume intersection checks, do a cheap distance check

easy example right here
local dist = (rootPosition - entry.Position).Magnitude
if dist > entry.Radius then
return
end
and then your checkpoint config can be somethin like
{
Position = Vector3,
Size = Vector3,
Radius = 30
}
- avoid cframe math bcause thats too performance heavy
local rootCFrame = root.CFrame
insstead you do real math like this!
local offset = rootPosition - entry.Position
if math.abs(offset.X) <= entry.HalfSize.X and math.abs(offset.Y) <= entry.HalfSize.Y and math.abs(offset.Z) <= entry.HalfSize.Z then
- cache the anti cheat just once! look, you do this in the loop:
getCompiledAntiCheatForTower(rs.Tower)
but you could js do it when the run starts
rs.CompiledAntiCheat = getCompiledAntiCheatForTower(tower)
then inside the loop you just do:
local compiledAnti = rs.CompiledAntiCheat
so no recomputation or anythin else
- only run anticheat for players in runs instead of scannig all players in the server. example:
for player, rs in pairs(RunServicePlayers) do
- use heartbeat and not
while true do
RunService.Heartbeat:Connect(function(dt)
rs.AntiCheatTimer += dt
if rs.AntiCheatTimer >= 0.15 then
rs.AntiCheatTimer = 0
--the anti check code here
ya thats all from me! in the end you shoulld get something like this
if rs.InRun and rs.CompiledAntiCheat then
if now >= rs.NextAntiCheatTick then
rs.NextAntiCheatTick = now + 0.15
local char = player.Character
local root = char and char:FindFirstChild("HumanoidRootPart")
if not root then return end
local rootPosition = root.Position
local nextIndex = rs.AntiCheatProgress + 1
local entry = rs.CompiledAntiCheat.Checkpoints[nextIndex]
if not entry then return end
if (rootPosition - entry.Position).Magnitude > entry.Radius then
return
end
if antiCheatCharacterTouchesOrCrosses(entry, rootPosition, rs.LastAntiCheatRootPosition) then
rs.AntiCheatProgress = nextIndex
rs.LastAntiCheatCheckpointElapsed = now - rs.StartTime
end
rs.LastAntiCheatRootPosition = rootPosition
end
end
ok hope al lthat makes sense ![]()
Thank you so much, I’m going to implement this now
I have a question though - some checkpoints are very small, so will the lower frequency cause it to possibly miss if the player reaches it?
yes there is a higher possibility if the checkpoints are rlly small, so you should increase the frequency specifically just for that small checkpoint ![]()
Is there a way I can make my script be a bit faster for smaller checkpoints?
This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.