Using fastcast to check a shots validity?

Using fastcast i created a gun system whose bullets have velocities and bullet drop.

I have a hit RemoteEvent which tells the server that a player has hit another player, and would like to do a security check on the server to see if the bullet has passed through any walls.

Is it possible to replicate that shot on the server immediately?

So that if the server gets told that a bullet has hit, it would “re-cast” that bullet from its origin point to its hitpoint, but it would have to do it immediately, because my bullets have velocities and bullet drop.

1 Like

If you’re asking if it is possible to immediately replicate something from the client to the server, then no. The only, secure, way to do this is via remote events.

You could try checking to see if the bullet came in contact with anything that wasn’t a parent of a character by sending the part that was hit though your remote event to the server? Although it’s possible I’m missing your question here.

1 Like

Lets say a bullet is fired on a local client, and takes 10 seconds to hit its target.

Using RemoteEvents I would then tell the server that this shot has hit something, and to check if that shot was valid, I would like the server to fire its own bullet to test if that local bullet really hit something.
So, to keep it short, I want to cast the bullet a second time, but this time on the server, to check if there were any obstacles between the origin point and hit point.

But here’s the problem: The bullet spent 10 seconds flying, and I can’t just wait for 10 seconds until the server verified the shot.

So my question is if I can cast this bullet “Immediately”, so that it moves from the origin point to its hitpoint in the matter of a few microseconds.

1 Like

Well, could you fire your remote event the second the shot is fired so they will fire together? Then if they both only hit the same point, and no other points you know it didn’t hit a wall?

Like firing the event it when the shot is first made, and the information needed to replicate it on the server, then once it has had impact, fire it back to compare the potential obstacles? (If that makes any sense)

Yes that makes perfectly sense and is exactly what I am doing right now, but I thought that that would be performance-heavy because only a small percentage of shots actually hits something, so a lot of % of bullets fired which don’t hit anything were “replicated” to the server (= fired a second time on the server) without a reason.

A better approach would be to only clone the bullet if the client tells the server it hit something, but to do that I would have to find a way to “replay” a bullet with bulletdrop “immediately” ( = e.g. a bullet which flew for 10 seconds before it hit something would have to be replayed on the server in under a quarter of a second).

But I don’t know if that is possible with FastCast and would appreciate help on this.

I don’t think it would be too bad if you keep most of the handling on the server, and only used the client for event firing, but I do see what you mean. Maybe you could “replay” the shot by sending the remote event with the information needed to replicate the shot on the server if the client hits something? Might be a little slow but that could work.

Yeah but like I said, if that shot gets replayed on the server it would take multiple seconds until that replay is done. My bullets don’t immediately hit something, they move a little slowly

Yeah then I’m not really sure of any solution to your problem. Best of luck though!

Yes you should be able to raycast it immediately. I forget exactly, but the fastcast module either takes a starting position, velocity and elapsed time input and returns the position of the projectile or takes a current position, velocity and delta time. Either way, you could “instantly” model the bullet’s trajectory up to some maximum distance/time on the server and then raycast through those points. However, I’m not sure how performant this would be since usually you are raycasting over substantially longer times.

2 Likes

Unfortunately fastcast doesn’t support immediate raycasting but we can achieve that ourselves by raycasting in segments along the projectile motions equation.

I’m assuming the fastcast bullet is client sided with a remote telling the server “Hey I fired” and a remote telling the server “Hey this bullet I fired hit” and the system uses a bullet ID method like @BIackShibe fps framework tutorial credits to him. He uses a math.random method of generating an ID, I’m using HttpService Generate GUID to generate a unique ID (similar methods random number). (For obvious reason client sided = better responsiveness)

This will be the first sanity check of detecting walls which @DevSneed is concerned about, makesure to set raycast params to only detect walls, don’t want to hit anything else during this instantaneous projectile path.

raycastAlongQuadraticPath
--g gravity, v velocity, t time, p initial position
local function projectileMotionEquation(g, v, initialPosition, t)
--See Ego Mooses tutorial on integrating projectile motion equation
	return 0.5 * g * t ^ 2 + v * t + initialPosition
end

local function raycastAlongQuadraticPath(g, v, initialPosition, endTime, raycastParams)
	local segments = 10*math.round(endTime*3) -- based on default beam adjust if needed
	local timeGapIncrements = endTime/segments

	local currentTime = 0
	for i=1, segments do
		local point1 = projectileMotionEquation(g,v,initialPosition,currentTime)
		local point2 = projectileMotionEquation(g,v,initialPosition,currentTime+timeGapIncrements)
		local direction = point2-point1
		local rayResult = workspace:Raycast(point1,direction,raycastParams)
		if rayResult then
			return rayResult
		end
		currentTime += timeGapIncrements
	end
end

Now the question is how do you find the end time in which the projectile has reached the target?

Personally I use the closest distance between the projectile path and the instance hit position. Assuming the projectile path has been validated by the server already (projectile origin is close to the player and all that).

pointToQuadraticClosestPointAproximation
local bulletExpireTime = 30
local timeIncrementToVelocityRatio = 0.05/1500 -- balance iteration count and velocity speed accuracy

local function pointToQuadraticClosestPointAproximation(point, g, v, initialPosition)
	local increment = v.Magnitude*timeIncrementToVelocityRatio
	
	local currentTime = increment --seconds
	local curveToPointVector = point-projectileMotionEquation(g, v, initialPosition, 0)
	local previousDistance = (curveToPointVector).Magnitude --Measure initial distance
	while currentTime < bulletExpireTime do
		local vectorDistance = point - projectileMotionEquation(g, v, initialPosition, currentTime)
		local newDistance = vectorDistance.Magnitude
		if newDistance > previousDistance then
			break --already found minimum distance
		end
		curveToPointVector = vectorDistance
		previousDistance = newDistance
		currentTime += increment
	end
	local timeAtClosestDistance = currentTime
	return previousDistance, curveToPointVector, timeAtClosestDistance --closest distance that is within bullet expire time
end

You could solve this problem as an optimization problem known as Point Quadratic distance but I don’t want to solve a cubic equation so I’m using this approximate method which guesses until the bullet expires.

Here is an illustration of what it does using beams:

On the client I moved the target closer like an exploiter:

The server will detect this error between the projectile path created and the server object, the threshold will depend up to you. Higher the threshold more forgiving to laggy players but eh it’s your choice.

This function will return the end time when the projectile should hit the instance with some error management because of client-server delays and all that. How you deal with that error is up to you.

Please comment if there are any more sanity checks I should be covering. This will also help my in my game.

14 Likes

Wow, thanks for the detailed reply!

Sorry for replying so late, but I took a small break from developing and saw this only now.
I’ll definitely try this out soon and get back to you in case I have questions, thank you so much!

(n.b. this reply was added as a response to a request for help from OP, sorry for the bump otherwise! It’s just important to have this be noticed by everyone)

The best option is to not do the true hit simulation on the client. The fatal flaw of your system is in the first paragraph: “I have a hit RemoteEvent which tells the server that a player has hit another player”

Overall, as a result of this, you are looking at this problem from the wrong angle. You can’t “resimulate” a bullet in this regard for validation - FC is unique because of the time that it takes for a bullet to travel. Unless you were to intricately record every single physics action on the server, you would not be able to verify the validity of a shot.

Observe how most other online games handle this. An example I personally am very familiar with is Team Fortress 2 – say, Soldier’s rocket launcher. When the client clicks, they send a request to the server saying “I want to fire this”. It yields until the server replies with a yes/no, and if it is yes, then the client fires the projectile for visuals while the server simulates the shot.

While this option is poor for the people who have high ping, it is ultimately the safest and is considered standard across FPS games as a whole (consequently, this issue is why many weapons will use hitscan when possible).

It may be worth it to remind you - even if this is not why you did simulation on the client, this is just so that everybody knows - that doing simulation on the server is not “lossy” in any way. This is a misconception created by physics replication e.g. using a physical part. FC was designed for the express purpose of replacing physics behaviors with parts. I would not allow the module to have the same issue, lest it be worse than useless as a result. One of the worries I always see is “The client is in a different place for the shooter’s screen than they are on the server!” - which doesn’t actually matter in the first place. People seem to forget that the visual latency is mutual, for the target and for the person shooting. So ultimately, they cancel eachother out by the time the data reaches the server, and by the time it begins simulating on the server too.

While this will require you to rebuild your code that handles this, I need to reiterate that using the client to tell the server when a hit occurred is a flaw that will render your game vulnerable no matter how much you try to validate it and check it. The proper solution is to simulate on the server, and let the client handle rendering the bullet. Once you do this, all validation methods can be tossed out. No need to resimulate, no need to make sure the bullet wasn’t teleported or altered during runtime, none of that.

12 Likes

REALLY late reply but I am doing this same thing and if two players are moving in one direction facing each other, when i fire a shot, even if i clearly see it going through the other player (I use another fast cast for visuals on the client), it doesnt deal damage. Is there any way to make ping not matter and at the same time make it secure since if its only client sided, the exploiters can just change the speed of the bullet

heres what i initially used:

local function GetPositionAtTime(time: number, origin: Vector3, initialVelocity: Vector3, acceleration: Vector3): Vector3
	local force = Vector3.new((acceleration.X * time^2) / 2,(acceleration.Y * time^2) / 2, (acceleration.Z * time^2) / 2)
	return origin + (initialVelocity * time) + force
end

-- A variant of the function above that returns the velocity at a given point in time.
local function GetVelocityAtTime(time: number, initialVelocity: Vector3, acceleration: Vector3): Vector3
	return initialVelocity + acceleration * time
end

local function RaycastTrajectory(origin, direction, maxDistance, raycastParams)
	-- Perform the raycast
	local result = workspace:Raycast(origin, direction * maxDistance, raycastParams)
	return result
end

function Validation.ValidateTrajectoryWithCollision(startValues, result: RaycastResult, velocity: Vector3, elapsedTime: number)
	local currentTime = 0
	local currentPosition = startValues.Position
	local currentVelocity = startValues.Velocity
	local timeStep = 1/60
	
	local params = RaycastParams.new()
	params.FilterType = Enum.RaycastFilterType.Include
	params.FilterDescendantsInstances = {result.Instance}

	while currentTime < elapsedTime do
		local nextPosition = GetPositionAtTime(timeStep, currentPosition, currentVelocity, startValues.Acceleration)
		local direction = (nextPosition - currentPosition).Unit
		local distance = (nextPosition - currentPosition).Magnitude

		local raycastResult = RaycastTrajectory(currentPosition, direction, distance, params)

		if raycastResult then
			-- Collision occurred
			local collisionTime = currentTime + ((raycastResult.Position - currentPosition).Magnitude / distance) * timeStep
			local finalPosition = GetPositionAtTime(collisionTime - currentTime, currentPosition, currentVelocity, startValues.Acceleration)
			local finalVelocity = GetVelocityAtTime(collisionTime - currentTime, currentVelocity, startValues.Acceleration)

			-- Compare with the provided result
			local positionError = (raycastResult.Position - result.Position).Magnitude
			local velocityError = (finalVelocity - velocity).Magnitude
			
			if positionError <= POSITION_TOLERANCE then
				return true, finalPosition, finalVelocity
			else
				return false, finalPosition, finalVelocity
			end
		end
			
		
		currentTime = currentTime + timeStep
		currentPosition = nextPosition
		currentVelocity = GetVelocityAtTime(timeStep, currentVelocity, startValues.Acceleration)
	end
	
	print("no collision")
	
end

this basically takes the initial position, velocity, and acceration of the cast, and the time it took to hit, and simulates the cast in one frame using a while loop, if it does hit, it compares the final position and velocity to the actual hit and returning true if the errror is within the tolerance.

my only issue with this is that during the delay between the client and server, an object could get in the way of the trajectory and cause the validation to fail, but it shouldnt be much of an issue if there isnt a lot of flying unanchored parts (for example if your game has destruction).

this should be the closest you got to what you asked.

edit: just realized this thread is solved already, whoops.