Lag Spike Reduction?

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?

1 Like

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.

1 Like

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

1 Like

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 :face_exhaling:

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) :smile: :smile:

2 Likes

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!

2 Likes

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

1 Like

Alright, thank you so much :smiley: 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 :face_exhaling:
anyways heres what i found in your scirpts that needs fixing:

  1. 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

  1. 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

  1. Before doin expensiv volume intersection checks, do a cheap distance check :eyes:
    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
}

  1. 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

  1. 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

  1. only run anticheat for players in runs instead of scannig all players in the server. example:
for player, rs in pairs(RunServicePlayers) do

  1. 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 :face_exhaling:

1 Like

Thank you so much, I’m going to implement this now :slight_smile: 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 :eyes:

1 Like

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.