I’ve been trying to implement some lag compensation into my raycasting guns for a FPS. So far I have all of the players latency stored in a map, and I know I am suppose to keep the player’s ‘n’ previous positions (I guess through a loop like with the latency). But just not really sure how to calculate how far to go back in the past based on the players ping and their positions.
What I think is suppose to happen is:
Shooter hits victim, sends info to server.
Server then verifies the hit by rolling the victim’s position back by the shooter’s most recent network latency.
To accomplish the rollback you would just do time shot was taken - latency? So I would have to keep timestamps on the player positions and when the shooter shot? Am I over thinking this or just wrong?
I don’t think it’s possible to make up for ping. A high ping will make the delay from client to server more noticeable, communication is slow. To make up for a high ping you would somehow need to record the future, which isn’t possible.
CSGO uses a system that compensates laggy clients, which you can sort of implement, too:
Detect which players are lagging more than others (you decide how this is done)
Seperate players with average+ connections and poor connections.
Send information of the enemy CFrames to the laggy clients before you send it to the strong clients (you can decide on the time delay).
This allows the laggy clients to be compensated, because it balances out the gameplay. This prevents players with strong connections from dominating the game purely because they can see the enemy first due to their good broadband.
It’s not perfect but you can get their move direction and get the studs/second based on the walkspeed, use that speed and multiply it by the negative of the movement direction (property of the humanoid) and try that? That’s the closest thing I can think of atm
Let the client send the direction they shot and whether it hit or not. The server can check if the ray was within a range of x studs of the character depending on their lag. Using previous positions, you can also check those. That way, laggier clients can still hit what they see.
Correct, I already have all those metrics/knowledge. I just need to figure out how far to go into the past (yes, I know it is based off their latency), but how do I actually do that. Like I suggested in the OP, I would need to know their positions with timestamps to know where they were based on what time.
There have been many, many topics created for this question. I’ve answered many myself. If you go to my profile and search “client lag” you’ll get a good list.
I would probably record a tick for when the victim’s CFrame was recorded using GetPropertyChangedSignal or Heartbeat, and whenever the shot was fired, record the signal’s tick and wait for the next update from the victim’s position, and lerp between the last CFrame and the current one, using the shot’s tick as basis for the alpha.
Pseudocode:
-- function to be connected to an event
-- both are players
function OnShotFired(shooter,victim)
local shotTick = tick()
--not a property, would be recorded somehow
local lastVTick = victim.LastTick
local lastVCFrame = victim.LastCFrame
local hrp = -- victim's HRP
hrp:GetPropertyChangedSignal("CFrame"):Wait()
local newVTick = tick()
local newVCFrame = hrp.CFrame
local a = (shotTick - lastVTick) / (newVTick - lastVTick)
local dummy = Dummy:Clone()
-- somehow simulate what pose the victim had
dummy.CFrame = lastVCFrame:Lerp(newVCFrame,a)
-- blacklist for the victim's actual character
local hit = SimulateShotFor(shooter)
if hit:IsDescendantOf(dummy) then
victim:TakeDamage(--[[damage]])
end
end
You will probably want to store extra data on how the gun was fired, specifically its position/orientation, as well as maybe the CFrame of the victim’s limbs, or otherwise the approximate animation used.
It might not work because:
It’s guaranteed to have at least a little lag.
It’s simply not accurate.
This may not actually be ping based, and may actually be based on how Roblox lerp’s its CFrames on default between pings, which is not what we want. How to actually get ping-based CFrames, I have no idea, and deserves an engine feature thread.
You would be able to create ping-based CFrames using a RemoteFunction that fires to the client and back. Under that structure, a new remote would have to be created for each player since there is no :InvokeAllClients method:
--on player added(player)
local pinger = Instance.new("RemoteFunction")
while true do
--player might have to return something, idk
pinger:InvokeClient(player)
-- record CFrame and tick here
end
It might still be off because remote functions are round trips, but at least it’s ping-based.
I have read many topics related to this question, both on the forums and outside the forums. Also, I have read many of your answers to this similar question, but while reading through all of that I still can’t seem to solve my problem, thus this post. I do however think you did an excellent job on this post in particular: Racing game versus exploiters? - #12 by IdiomicLanguage
The only thing that I didn’t get out of that post (you hinted at it, but didn’t explicitly say it), which is what I have been searching for, is the formula/method used to interpolate the victim’s current position and the victims position of when the shooter shot.
If you have how laggy they are (make sure it is not exploitable), then record the character’s position each frame. The client should send the hit position, which you make sure is on the ray that they send was the direction they shot. When a client says they hit a player, go x frames backward (depending on the lag / ping that you said you have recorded (If 60fps, then 60 frames back with a second lag)). See if the original hit position and the position that frame are within about 6 studs. If they are, then count the shot. If not, do not.
Basically, you make sure the point the client said they hit is on the ray of direction that they shot. Then, go x frames backward to that position recorded. Use .magnitude to get the difference between the 2 points, and if they are close enough, count the shot.
I don’t think it’s possible to find out how laggy a player is without asking the client, which can be exploitable, and lead to a exploit called backtracking. Using a backtrack exploit, you can hit players on positions they used to be at.
Even with the client just providing the direction they wish to shoot, you still have this problem. This is not due to the backtracking. The exploiter could send the direction to the character they want to shoot.
Exploit Protection
You can protect yourself from exploits like this. Make sure the hit point that they claim to have hit is on the direction ray they also provide. Make sure the ray has the correct start and end positions, and the direction is the same. In reality, a exploiter could already fire the right direction with a aim bot even without resisting the client. Check the distance between the hit point and the character that frame. It’s not possible to fully protect this method, but even a regular gun would be able to be cheated.
The unsolvable problem
That is true, and requests can be spoofed to simulate different pings. In the end, every solution to this problem will have benefits and negative consequences. There is no full proof way to do this without opening the game to exploiting vulnerabilities. Any time you trust the client, you open these. By checking reasonability and making sure to make checks (Is that point on the ray, is the distance remotely close) you can help to deter exploiters. Unfortunately, this is a problem has been attempted to be solved many times over, and sadly it is one of those unsolvable problems. To give chances to players, you also give chances to exploiters.
Summary
You can not solve this problem without giving exploiters a chance to use it. You can however check for reasonability and dent unreasonable requests. While there is no solution without cons, you can still try to find a middle ground and protect it.
This is what I was looking for! So if I were to limit player compensation at 200ms, is there a way to know the max ‘cycles’ I would have to keep track of, or it seems that frame rate comes into play there. Just keep max 12 (60/5) then, because cap is 60 fps?
See, the server framerate will vary too. It will not always be 60fps, though that is the normal fps. If you organize your tables like this, then you can remove them based on how long they have existed (I.E. 200 ms)
How to cap ping at 200ms
PingEvent.OnServerEvent:Connect(function(player,ping)
ping = math.clamp(ping,0,0.2) --If ping = x seconds, then this will keep ping between 0 and 0.2 seconds
end)
--Format to store in
local characterFrames = {
Bob: { --Each player has a index, which contains their frame data, each frame contains the following:
Pos: Vector3.new(0,10,0) --Position
At: os.time() --Time they were at that position, so you can find the closest time (or average between them)
}
}
--Checking to see if the frame can be destroyed
local frame = caracterFrames.Bob[1]
if os.time()-frame.At > 0.2 then --Older than 200 ms
characterFrames.Bob[1] = nil
end