How to make a basic Anti-Fly script

Introduction:
In the Roblox Engine, all changes a client makes that isn’t a part it networkly owns, is not replicated. However, characters, can be directly controlled by clients. This means the client can make their character move, rotate, speed up, speed down however it decides. This is so that you can smoothly control you character on your own client. However, exploiters exploit this by manipulating their character to fly, teleport, fling others, and more. In this tutorial, I will be explaining how to stop one of these; Flying

How it usually works:
as the server, you want to be checking how far players are from the ground. Sometimes, if they’re above the limit you set it could be from the client being laggy on their side, or maybe a really lucky jump. So you usually have to add a “strike” system. The server uses a raycast to check for any ground, and if it finds some, it moves on to the next player. If the player continues to “fly” you can kick them

Step one: set up variables and the strike tables
This is from my free-to-use basic anti-fly

local FlySettings = {
	StrikesUntilRefresh = 6; -- if the player is above MaxHeight this many times, they get refreshed
	StrikesUntilKick = 5; -- when a player gets refreshed this amount of times, they get kicked 
	MaxHeight =  Vector3.new(0,-15,0); -- if distance between player and ground is above this, the player is consdiered flying
	Intervals = 15; -- amount of heartbeats until the game checks all players
	AddStrikeIfPlatformStanding = true; -- if player is airborne, add an aditional strike for having platformstand
};

local PlayerStrikes = {};
local KickStrikes = {};
local Players = game:GetService("Players");
local RS = game:GetService("RunService")

Players.PlayerAdded:Connect(function(player) -- adding players to the dictionaries as they join
	player.CharacterAdded:Connect(function(character)
		PlayerStrikes[player.Name] = 0; -- playerstrike restarts every CharacterAdded
	end)
	KickStrikes[player.Name] = 0;
end)


Players.PlayerRemoving:Connect(function(player)
	PlayerStrikes[player.Name] = nil; -- removes the player from the dictionary since they left the game
	KickStrikes[player.Name] = nil;
end)

next, we’d want to make a function that “refreshes” the player. This re-loads the character, and re-places them where they were before, and removes their forcefield. It basically removes any bodymovers in their character that would’ve made them fly. If this was a false positive, it also wouldn’t interfere much with the player, since on their end they just stopped moving for a few frames

local function RefreshPlayer(player, pos) -- this function reloads the player, removing any bodymovers and returning them back to their spot as if nothing happend
	player:LoadCharacter();
	player.Character:SetPrimaryPartCFrame(CFrame.new(pos));
	local ForceField = player.Character:FindFirstChildWhichIsA("ForceField");
	if (ForceField) then
		ForceField:Destroy();
	end
end

Now, we want to make a loop to check each character if they’re near enough on the ground or not. We first make a RaycastParam to blacklist all the characters, so that the raycast doesn’t hit the character itself. I Connected this to RunService.Heartbeat Keep in mind you shouldn’t loop every heartbeat, since updated player positions wont send for a bit, and there wont be much time for the characters to move. We also want to make a variable to check how many heartbeats have gone since the last check of all the players. You should add an additional strike if the character is PlatformStanding, because almost all fly exploits have the character PlatformStanding.

CurrentInterval = 0;
RS.Heartbeat:Connect(function()
	if (CurrentInterval >= FlySettings.Intervals) then
		CurrentInterval = 0;
		local Characters = {}; -- ignorelist for raycasting
		for _, player in pairs(Players:GetPlayers()) do
			if (player.Character) then
				table.insert(Characters, player.Character);
			end 
		end
		local FlyParams = RaycastParams.new() -- creating the params for checking if the players are on the ground or not
		FlyParams.FilterType = Enum.RaycastFilterType.Blacklist;
		FlyParams.FilterDescendantsInstances = Characters;
		
		-- checking each player --
		for _, player in pairs(game.Players:GetPlayers()) do
			if (player.Character) then -- if there's no player.Character it can mean the player is dead, but correct me if im wrong
				local Root = player.Character:FindFirstChild("HumanoidRootPart");
				local Humanoid = player.Character:FindFirstChild("Humanoid");
				local Head = player.Character:FindFirstChild("Head");
				if (Humanoid and Humanoid.Health > 0) then -- making sure player isn't dead
					if (not Root) then -- this is a sign of bypassing anti-TP scripts, so we're refreshing the player to their head's position
						RefreshPlayer(player, Head.Position);
						continue;
					end
					
					local FoundGround = workspace:Raycast(Root.Position, FlySettings.MaxHeight, FlyParams);
					if (not FoundGround) then -- if the ground is not found, meaning the player is airborne thus, we give them a strike
						if (FlySettings.AddStrikeIfPlatformStanding) then
							if (Humanoid:GetState() == Enum.HumanoidStateType.PlatformStanding) then
								PlayerStrikes[player.Name] += 2;
							else
								PlayerStrikes[player.Name] += 1;
							end
						end
						if (PlayerStrikes[player.Name] >= FlySettings.StrikesUntilRefresh) then -- if the player was airborne for too long
							if (KickStrikes[player.Name] >= FlySettings.StrikesUntilKick) then -- player kicked for flying often
								player:Kick();
								continue;
							end
							local GroundPos = Root.Position; -- position where we're gonna refresh (respawn) the player. It's the root part incase if no ground is found in the next raycast 
							local FindingGround = workspace:Raycast(Root.Position, Vector3.new(0,-300,0), FlyParams);
							if (FindingGround) then
								GroundPos = FindingGround.Position+Vector3.new(0,5,0) -- found the ground, so the player will be respawned on the ground
							end
							PlayerStrikes[player.Name] = 0;
							RefreshPlayer(player, GroundPos); -- refreshing the player for beign airborne
							KickStrikes[player.Name] += 1;
						end
					end
				end
			end
		end
	end
	CurrentInterval += 1;
end)

Conclusions
just like the title says, this is a basic anti-fly script. Each game is different, and there could be a bypass (or false positives) if your game had some “plane” or something allowing the player to fly. Please feel free to reply if you have questions, or if I made a mistake.

You can get my full script from this tutorial here

14 Likes

The only problem with this is that if the client makes its humanoid platform stand, it won’t get replicated to the server.

Another insight is what would happen if a player were to jump off a high place many times? Or if the game is an obby and there is a hole below them? Would this set off the script?
A cheater could also fly low to the ground.

2 Likes

that is true, the PlatformStand property doesn’t get replicated, but the humanoid state does from Humanoid:GetState() (I did test this out)

You would need to be airborne roughly 3+ seconds, which is kinda hard to do. I’ve had a similar version of this script in my game for the past few months, and there hasn’t been any reported false positives. And I personally haven’t had a false positive on myself.
If the false positive amount was high, increasing the strikes to be refreshed, or better, adding another method of “verifying” strikes that’s relevant to a specific game would generally solve it. (like checking if the player was in a place where they should fall for a while)

2 Likes

You should never kick the player for any form of anti-exploit / guard like this. Especially in this case, humanoid behavior could slightly, or majorly change, the physics engine could change, etc. Or a player could just straight up lag and be stuck in the sky for 5 seconds or so, and now they’ve been kicked and lost their (non-datastore) session progres

2 Likes

while this does make sense, keep in mind players rarely get refreshed in games that don’t involve flying or massive heights. The player would have to continually be above the ground even after being refreshed multiple times. I do know of some exploits that re-position players back into the sky (with bodymovers) after being refreshed, which gets around the script entirely. Which is the point of the kick.

the player would have to contradict the server’s correction of the character multiple times before a kick

1 Like

The best way in my opinion to check if a player is flying is to run magnitude checks every second or so. This method saves the HumanoidRootPart’s position and again in the next second. If the distance travelled between those two points is farther than a player could normally walk, then you can anticipate it as flying/teleporting and kick.

Also, instead of looping through all the players in the game every now and then, you should use game.Players.PlayerAdded and run your anti-exploit in there, you could keep your PlayerStrikes system a datastore so that the strikes save across servers.

2 Likes

These are also good ideas, especially the magnitude check. However playerstrikes shouldn’t be stored since they reset when a player dies. But the amount of kicks could be, so that if a player is kicked many times for flying you could investigate, or outright ban the user if they get kicked X amount in a certain period of time

I decided not to add the magnitude check, since I mainly associated it with speed hacking/teleporting. But it along with the other checks would make an even better anti-fly

1 Like