I’m trying to figure out how I would accurately portray where a user’s ray will go? Currently, it selects a random spread and draws a ray and sends that to the server to replicate that for everyone else.
But this isn’t good as an exploiter could make the ray go where they wanted right? So the only thing I should be passing to the server is where the client is aiming. But how do I make it that it draws the same ray with the same spread for both cliënt and server? If I drew it on both client and server it would be 2 different spreads and what would be shown on the client wouldn’t be accurate with what’s happening on the server. How would I go about solving this? (The client’s ray is for cosmetic purposes)
Some games do just have different spreads on client and server, CSGO for example.
I’ve hypothetically thought about a solution to this before, but never tried it. The idea is, each player has a table full of pseudorandom numbers, synchronized between the server and client, which can be generated by using maths.randomseed(Player.UserId)* and then just repeatedly calling math.random to fill a table on both the client and server.
Then, every time a bullet is fired, both the client and server read the next number from their table to calculate bullet spread with. Because the tables should be the same on both the client and the server, they will both calculate the same bullet spread independently, without the server having to rely on the client to tell it where the spread should be. The only issue is if the client, for example, fires a shot locally that is then not replicated to the server for whatever reason, the client is now further ahead in the table than the server, and the spread is no longer the same, so you’d have to add something to periodically check they are both in-sync.
Theoretically an advanced exploiter could use the table to predict the spread and use it to make their aimbot 100% accurate even with an inaccurate gun, which I think is why this isnt used in CSGO. But honestly, this is Roblox, if someone is already aimbotting then it’s rather irrelevant if it has that extra bit of accuracy.
*There’s nothing special about using the Players UserId, it just needs to be a number thats the same on both the client and server.
I’ve experienced this problem before and I’m pretty sure I have a solution to it. On the client, it would get the aiming direction and send it to the server when clicking. The server now does the raycast checking, and creates a ‘spread’ variable when done raycasting. It would send this to all of the clients, which creates the bullet effects and uses the ‘spread’ variable to offset the bullets location. Because of this, it both can show an accurate bullet on all of the clients and both the client and the server will have the same spread.
Although I hadn’t fully implemented it, I did experiment a bit in the past with this. From my experience, the way I found best to do this (not only from ease of development but keeping the code clean and maintainable afterward) was to just use a bit of math instead of any preexchanged table. I sent 3 numbers to the server when a shot was fired.
Where the ray originated (barrel of the gun)
Where the target was (mouse location)
A random number generated by the client
I wrote my own random number generator using those 3 as inputs (for no great reason other than that I wanted to) but I’m pretty sure if you use that as a seed, roblox’s standard random would generate the same result on the client and server. But the idea is the same, you use those 3 as inputs on the same random number generator on both the client and server, and you’ll get the same pseudorandom result.
Given the shot’s origin and target are part of the calculations, writing an exploit that generates the perfect random number to get 0 recoil is unfeasible, while still taking few calculations and being maintainable code.
Based off Player 1’s mouse hit position, determine a spread position (offset from the mouse hit position)
Player 1 does a raycast on in its own environment (cosmetic)
Player 1 sends data to the server: (origin <X, Y, Z>, spread position <X, Y, Z>)
Server bounces the data to the other clients (Player 2, etc.) - for cosmetic effects
Server does its own raycast (to determine damage or something idk)
Basically, rather than having the client AND server figure out the spread, have the client figure out the spread, and then tell the server the new target <X, Y, Z>.
Currently, it selects a random spread and draws a ray and sends that to the server to replicate that for everyone else.
But this isn’t good as an exploiter could make the ray go where they wanted right? So the only thing I should be passing to the server is where the client is aiming.
I think the concern here is how to make a secure method in which the client draws a cosmetic ray, then the server draws the real ray and runs needed damage calculations, and the rays are the same. In your case, the client could send incorrect data to the server to negate spread.
I don’t see how they could send “incorrect data” for the spread… (realistically)
And even if they managed to send incorrect data for the spread, it wouldn’t matter because it’s simply a target position (similar to mouse hit position) - it would be the equivalence of aiming somewhere else accidentally.
Example:
Player aims at the point <10, 8, 10>
To add inaccuracy, the new aiming point could be at <9.6, 8, 10> to simulate spread
The player then sends its position, and the off-centered aiming point to the server
Aimbotting shouldn’t be a concern. The reason why the server handles the damage logic raycast is so that it can assume that the environment and player positions are verified. For example, if a player is lag switching, or lagging in general, you can’t completely trust their local player position because it might be different on the server-side - which could give them a competitive advantage.
A good rule to follow is to never trust the client, hence why FilteringEnabled exists. If the client just tells the server to draw a ray at <9.6, 8, 10>, the server would be unable to verify that the shot takes spread into account because the server won’t know exactly where the player was and where they were aiming at the moment the shot was fired.
It’s an easy enough addition to calculate the spread randomly (but with the same random number generator) on both the client and server, and ensures that the spread and damage are both calculated on the server-side. This allows the server to not blindly trust the client.
Well the player should ALSO send a copy of its own player torso position to the server… and have the server verify that the server-side version of the player’s torso position is within a respected radius (maybe 2 studs idk)…
This is to verify that the origin position sent to the server is “okay”.
I think we agree on verifying the player is at the origin of the shot, though I think comparing the ray’s origin to the player’s location on the server-side is good enough (one less number to deal with). The reason for “good enough” is that you have to have a wide range of acceptance, because you don’t know how fast the player is moving. This can be tuned based on the game and the max speed you expect players to get to, though that’s outside the realm of the OP’s question. I think the question revolves around ensuring spread is calculated equally on the client and server, and not about verifying the player is actually at the location the shot originates from.
As a quick edit, another reason for using the ray’s origin instead of sending and comparing the player’s torso position, is that if the remote event call is faked, we can’t trust the parameters sent and should only be concerned with verifying the location of the shot. If you sent and compared the torso location, you’d also have to do another check to make sure the torso position sent is within a certain distance of the ray’s origin, and then things just start getting far more complex.
I don’t know how you could calculate your “real” torso position on the client while lagging, and then send that to the server, but yeah, it’s definitely something to consider