Accurately detect player landing

Hello! I have recently been having difficulty with coming up with a method to accurately detect when a player hits the ground, from a jump or otherwise, on the server. I have played around with a couple of different methods, but each one seems to have its own drawbacks that prevent the desired behaviour.

Attempt 1: HumanoidStateType
My initial train of thought was to simply detect when the players humanoid changes state either FROM freefall, or TO landed. However, this proves unreliable as it sometimes fails to register when repeatedly jumping:

local stateConnection = char:WaitForChild("Humanoid").StateChanged:Connect(function(old,new)
		if (old == Enum.HumanoidStateType.Freefall) or (new == Enum.HumanoidStateType.Landed)then
		warn("LANDED")
	end
end)

As the video shows, occasionaly this misses the landing. Doing this on the client does not give this issue. I assume it’s to do with the state not instantly replicating to the server?

Attempt 2: Raycasting
So I thought I’d be able to fire a ray vertically down from the player’s HumanoidRootPart, and if the distance between the rays hit and the HRP height (when standing) is less than a small margin then they must be grounded. However, the issue I had with this was that when repeatedly jumping, the HRP Y position doesn’t reliably return to a low enough (standing) value on the server before the next jump begins, meaning that I can’t determine if the player actually landed this way, not on the server at least.

Attempt 3: Part Direction
I then tried comparing the HRP Y position to its previous position in Heartbeat. But I couldn’t figure out how to do this. I can determine when the HRP changes direction, and from that determine when they jump. But to determine when they land, I need to check that a) the distance between the current and last position is less than some threshold value, and b) check that they were falling in the last heartbeat.

This was my attempt, but it is again very unreliable and sometimes fires multiple times during a jump, or not at all:

RunService.Heartbeat:Connect(function(DeltaTime)
	local pos = player.Character.HumanoidRootPart.Position.Y
	if math.abs(pos - lastYPos) > 0.2 then   --if we have moved significantly since the last heartbeat
		lastDir = math.sign(pos - lastYPos)
	else
		if lastDir == -1 then   --if we were falling but now havent moved significantly, we landed
			warn("Landed")
		end
		lastDir = 0
	end
	lastYPos = pos
end)

So, my question is how do I accurately and reliably determine, on the server, when a player hits the ground, even while repeatedly jumping? It seems like such a trivial issue, I am surely missing something obvious here! Or, since a player’s movement is client driven, is it actually as safe to simply detect a landing on the client and use a RemoteEvent to report this back to the server?

Thanks for the help!

16 Likes
while game:GetService("RunService").Heartbeat:Wait() do
	if script.Parent.Torso.Velocity.Y > 10 then -- Are they in the air?
		repeat game:GetService("RunService").Heartbeat:Wait() until script.Parent.Torso.Velocity.Y <= 0 -- When do they land?
		print("Landed") -- Landed
	end
end

Put this into a script and place the script into StarterCharacterScripts.
Just checks if the torso Y is moving, if so then it waits until it stops moving or is about to stop and when it does stop it prints land.

4 Likes

I am able to detect the player landing on the client, but I am trying to do the same on the server (or get an answer as to whether that is even the right thing to be doing). Your script will only work on the client, but also prints ‘Landed’ once the character starts descending from the jump instead of landing, because that is the point where the velocity becomes negative, rather than on landing

What’s the context of use? The RayCasting method client side would seem best to me, but how important security is for this action should be considered. Can someone get some advantage which matters by repeatedly reporting they have landed, or report they have landed in an incorrect state (e.g. on a flat plane not walking)? If not, or it only effects the one player doing it then it would probably be the best way to do it.

I don’t think you are missing anything obvious, the Character is controlled client side, so not everything is going to be known by the server.

If you wanted server-side detection, you’d probabbly need to run some kind of simulation detecting player jumps, simulate the expected positions, and if close enough, assume a land has taken place but I wouldn’t imagine anything using this type of method would be able to be 100% reliable/timely and quite resource intensive for what seems a mundane task.

2 Likes

My use case is that I have players forming trails behind them while they are grounded that kill other players. So I’d need a way to find when a player lands to avoid repeated jumping and never being grounded, and I’d ideally like to ensure a client can’t easily exploit this, hence why I wanted a server sided approach.

But as the character is client controlled, would there even be any security advantage to checking the state or position on the server? Could both of these be incorrectly reported by the client without any remote events anyway, and instead I’d need different sanity checks like the simulation you suggested?

You could just make a jump cooldown script to fix that issue. This is a LocalScript in StarterCharacterScripts. Unfortunately, this would most likely be 100% exploitable. Maybe you could try and figure out how to do this on the server but as we have seen, it is quite inaccurate.

local character = script.Parent
 
local JUMP_DEBOUNCE = 1
 
local humanoid = character:WaitForChild("Humanoid")
 
local isJumping = false
humanoid.StateChanged:Connect(function(oldState, newState)
	if newState == Enum.HumanoidStateType.Jumping then
		if not isJumping then
			isJumping = true
			humanoid:SetStateEnabled(Enum.HumanoidStateType.Jumping, false)
		end
	elseif newState == Enum.HumanoidStateType.Landed then
		if isJumping then
			isJumping = false
			wait(JUMP_DEBOUNCE)
			humanoid:SetStateEnabled(Enum.HumanoidStateType.Jumping, true)
		end
	end
end)

I would go about trying this on the server anyway but it will probably be inaccurate :confused:

2 Likes

You can put ServerScripts into StarterCharacterScripts. Also if you want it to be exactly when they land just make it == 0 instead.

Yeah a debounce could help, but I’d like to keep the jumping behaviour as normal as possible, and a cooldown takes away from that.

@StraightScared Wow, how did I not know that ServerScripts work there too :laughing:?! Anyway, the issue with that is the velocity is not garunteed to be exactly zero on landing while repeatedly jumping.

1 Like

I’m having the same issue. Seeing as this post has not been solved yet, I’m bumping it.

4 Likes

https://developer.roblox.com/en-us/api-reference/event/Humanoid/StateChanged

Humanoid.StateChanged:Connect(function(oldState, newState)

if newState == Enum.HumanoidStateType.Landed then

print(“Landed”)

end

end)

5 Likes

I had to edit this because I noticed it only worked when jumping. If you need to detect every time the player lands accurately, this may work.

local Hum = script.Parent:WaitForChild("Humanoid")

Hum.StateChanged:Connect(function(old, new)
	if old == Enum.HumanoidStateType.Freefall and new == Enum.HumanoidStateType.Landed then
		print("landed")
	end
end)

6 Likes

In reply to this, for handling the event on the server, I’ve set up a very simple couple of scripts.

Capture

The LocalScript:

local Hum = script.Parent:WaitForChild("Humanoid")
local event = game:GetService("ReplicatedStorage"):WaitForChild("RemoteEvent")

Hum.StateChanged:Connect(function(old, new)
	if old == Enum.HumanoidStateType.Freefall and new == Enum.HumanoidStateType.Landed then
		event:FireServer()
	end
end)

And on the server Script:

local event = game:GetService("ReplicatedStorage"):WaitForChild("RemoteEvent")

event.OnServerEvent:Connect(function(player)

print(player, "landed")

end)

8 Likes
local Character = game:GetService('Players').LocalPlayer.Character
Character.HumanoidRootPart.Landing.Changed:Connect(function()
	if Character.HumanoidRootPart.Landing.Playing then
		print('Player landed!')
	end
end)
-- LocalScript in StarterCharacterScripts

This only gets fired when the landing sound has been played

3 Likes

Yes.

(Sorry for bumping the topic btw)

NobleBuild’s method seems like the most reliable to me.