Making secure client sided raycasts

Hello everyone.

I am currently working on a weapon system and in that occasion, I am obviously using raycasts. I have read some contradicting information regarding this topic, so I reckoned my best shot would be to ask here. Essentially I want to make a responsive but also (as much as possible) exploiter proof.

I originally planned that to have the raycast be fired from the client and then do the raycasting (etc.) on the server. People told me to refrain from that since it could produce some latency and ruin the player experience, since the player won’t take damage at once (maybe). So I then had to rethink this, I found that the best solution, and most used solution, would be to have the raycast on the client and then try to validate/verify the results on the server. So my question is basically what I should verify? What information should I send, and how would I verify the results?
I also had one other idea, and that would be to have a raycast on the server also that would check.
So, my idea would be as following:
Raycast on the client
Verify results and take damage.
Have a raycast (after damaging the player etc.) to double check, and if it turns out that raycast doesn’t give me the same results as the client one, give the health back to the player (revoke all the things made, annul the results)
Would that be a good idea?

Looking forward to your ideas and thoughts. (So please tell me some things I should verify, and if you want, also tell me if the idea with the second raycast is good or bad. Thanks)
Best regards!

(I know there are some post that look alike, but trust me I’ve researched and haven’t found the answer I am looking for, whilst I also want feedback on the idea that I had.)

10 Likes

On the client, you definitely want to show all the visuals. I wouldn’t worry about changing their health on the client, but an indication that they hit a player should be good.

On the server, it get’s a bit more tricky. There are a lot of things you can check. The more, the better.

To give you an example: last month I created a traveling projectile. Here are the things I verify:

  • Start position of the throw. Is the player even near this position when they start throwing?
  • Throw Velocity. Is the velocity a realistic velocity?
  • Throw per second. Are they throwing 100 projectiles in a second when the client only allows them to throw once every 2 seconds?

In your case, instead of throw velocity, you would want to check two things: Where the ray hit and what it hit. Using this information, you can shoot your own ray on the server. If the server was able to reach that hit position, you can verify that there wasn’t a wall in the way.

Now this is where it gets tricky. You also need to check if what they hit is the same thing your server hit. However, do not forget that there is latency! On the client, a shot could have hit a player, but just barely miss on the server! This is where you have to decide if you want to compensate for that.

Trust the client more and just check if the player they hit is at least very close to where they said they hit it? Or don’t trust the client at all? They both have drawbacks. If you trust the client even a little bit, it can get abused. However, if you don’t trust the client, players with higher latency will almost always miss their shots on a moving player, even though on their client they are hitting their target every single time (this is super fustrating!)

There might be ways to get around this, but it isn’t trivial at all. One possibility is to essentially build a “time machine” where you can verify results in the past second. Another one is to use the player’s velocity to help go back in time and see if it hit.

Anyway, the last bit might be a bit extra for Roblox. Good luck!

7 Likes

Thanks for your extensive answer, I appreciate that!

Regarding the above comment, if I give the exact same hit position and starting position what could result in 2 different ray casting results? How come the server might regard it as a miss (if they have the same parameters)

Thanks

Edit:
Also, could you explain a little more about latency? Do you mean latency from the client to the server, so if someone has a bad connection it will take time for the server to “react”? Thank you.

Technically, you don’t shoot the ray just to the hit mark. You ray should be a normal length shot you would do on the client. However, the ray should at least reach where you hit.

Latency (aka lag) is the the delay from the client and server. An excellent connection would have a 20 ms delay. A terrible connection would be 200ms and more. If you click and shoot a player, it would take 200ms to reach the server. While on the client you hit the person right in the face, the server would regard it as a miss as it took 200ms for the server to do its own version of the shot. In 200ms, a player can travel a great distance (could be over a few studs!) which is enough for the server to regard it as miss. This is where the problem lies.

Many games combat this problem differently. Some games might have a 100ms charge time before a shot, giving the client enough time to tell the server “hey, I’m going to shoot this in this direction in this many seconds.” Other games do traveling bullets (which is what I did.) With traveling bullets, you can have the server “speed up” the shot once it receives it from the client, that way the client shot and server shot are in the same exact position. It’s not hard to do, but you have to synchronize a clock.

3 Likes

Hm. This is really more advanced than I thought.

  1. My first idea would be to have a raycast on the client that only gives visual feedback, like a hit icon on the player etc., and then fire the server which verifies and deals the damage. (And has it’s on raycast) - (Hmm, does this even work, I feel like all my solutions are terrible)

I really can’t fully get a grasp of this… (although I’ve learnt a lot from you)

Sorry if you feel like you’re spoonfeeding me, but could you give me some really vague pseudo code:
like:
client detects
fires server
server verifies etc. etc.

Because I am really confused and it’s such a hard topic, yet so important.

Thank you alot so far!

Client Script:

on click {
    fire ray
    tell server
    do all visual actions
}

Server Script:

on client event {
    verify_shot(info)
    if verified {
        do damage
        tell other clients
    }
}

func verify_shot(info) {
    check if player has gun equipped
    check if they have amo to make the shot
    check if initial shoot position is near current gun fire position
    check if fire rate isnt realistic - save the last tick() and compare it with the current tick
    
    shoot ray
    -- OPTION 1, NO TRUST
    act based on shoot ray result, return true
    -- OPTION 2, SOME TRUST
    make sure it reaches to or past info.hitposition
    check if info.hitpart is at least ~5 studs away from the info.hitposition
    if both above are true, act based on info's hitpos and hitpart, return true
}
11 Likes

Wow, thank you so much! You’re a champ!

What would you say is a reasonable false margin, like, how far can the player move within the time it takes fro the serve rto verify it?

And also, with the hit position, if there is lag (as you wrote) 5 studs is probably good. The biggest hitbox a player could have then is:
image
(5 studs to each side, hence 10^3)

Could you explain a little bit about the exploiting possibilites? Like, can a person fake lag and then be able to shoot 5 studs off and still get awarded?

Thank you a lot so far, I am starting to make the sytem right now and I will share it as soon as it’s done. Thanks @Ninja_Deer, you are truly a lifesaver :slight_smile:

1 Like

Since you are trusting the client a bit, then yes it’s possible exploiters can do something if they understand your system. If I were you, if you see a player where many times the server rejects the client (especially when they shoot more than they have bullets or shoot through walls) you should either kick them or send a Mod to the server to investigate.

1 Like

Hey. Thanks for your answer, I’m starting on a prototype right now! Looking forward to getting your feedback :slight_smile:
Thanks so far :slight_smile: !!!

1 Like

Hey, I just got one question regarding the

How would I do that? I am really out of ideas, so it would be grat if you could help me. Thanks!

And lastly, how would I do the server raycast comparison. Like, how would I see if the goes to the point (well that I know) but (say the player moved withing the latency time) and I have to check if it went to that position or further. Pure mathematically, how would I do that?

Since I obviously can do raycast[“Position”] == localRaycastPosition

how would I check if it goes further? I am just unsure. Thanks. Or do I do .Magnitude? Not sure…

Thanmks for answers!

So when you fire, you send the start position of the bullet to the server. The server should check if that start position is anywhere near the gun would typically fire (so from the actual gun handle for example.)

You could shoot a ray on the server to hit the position described by the client. If there’s a wall in the way and it’s not a moving wall, flag the player to get checked by a mod or potentially kicked. If the client said it hit x player and it actually did, go ahead and do damage. Otherwise, check if the position they claimed they hit is somewhat near the player using magnitude or whatever method you want. This is the risky part that can get abused, but you get better player feedback.

1 Like

Thanks for your answer!

But about this:

an exploiter could send one position to the server, and then another position to the raycast… So I cannot trust the position that the client gives me (if you get my drift). Is there any way I can guarantee that the value being sent to me, is the samea s the value used in the LOCAL raycast? I hope you get what I mean.

Thanks for your answer :slight_smile:

I just have some issues with correlating the server and client positions. As you can see in the images, if the player that was shot at moves a little to the sides, the bullet will go further out and hence be more than 5 studs away from the player, although this should (in my opinion) give damage.

This is maybe what the client registers:
image

And this is what the server might register (from the same info):
image
Here you can see that the ray traveled further since it wasn’t intercepted by the character, hence, being technically more than 5 studs away. So in that case, you should measure the distance like this:
image
See the green arrow, somehow need to null one of the axis in order for only the “side” distance to be measured.

What I basically have to find out, is, how to detect if the serverRay goes further than the localRay position and then only account for the side distance.

The issue though, is in order to remove the “front distance”, you have to know which one it is. Like, X,Y or Z. image
Since you can technically shoot in the Z, X and Y direction.

I hope you get what I mean. I’m not very good at maths and such, so it would be a real life saver if you could help me on this also!

Man, I really can’t thank you enough!

Edit: I found out that I could use something called: ClosestPoint(). BUT: it only works with the deprecated Ray. (Ray) So, back to square one.

Is there a built in function like :ClosestPoint(), but for the new raycast? Or is there something else I need to do?

Doesn’t seem like it for some odd reason…

Anyway, you can write your own function to do that. Google “Closest point on a finite line” and it should help.

Here’s a link I found that seems helpful. You’ll have to adapt it to Lua tho:
https://forum.unity.com/threads/how-do-i-find-the-closest-point-on-a-line.340058/

Sorry to necro-bump, but I’m also trying to follow along and use this thread for my own gun system.

Why don’t you just make the client pass the intersection point of the raycast from the client? The server can just raycast from the position from the gun to the intersection point and it won’t extend pass the character. (Please correct me if I misunderstood)

Edit: Nevermind, using intersection seems to not work for me

Wouldn’t this be a risky check since the player can unequip or drop the gun?

Sorry for this off-topic reply. Just wanted to say that I‘ve been busy, but that I will give you a reply as soon as possible!

1 Like

I don’t understand how this is risky. It’s risky to not check if they have the correct gun equipped.

3 Likes

Yeah ten days before I was thinking the player could shoot somebody, and then quickly unequip right after, breaking the script or something. Then I realized that would be impossible because the event literally fires instantly, so there would be no way that the check would even break from anything the player does right after.

Sorry for the logic I had, I can assure that I do check if the gun is equipped on the player now hehe

2 Likes