Two problems with vehicle turret system

I had an issue with vehicles sharing network ownership that I just managed to solve with weld manipulation instead of relying on constraints. However, this has introduced some new problems:

  • The turret CFrame seems correct when the vehicle spawns (image 1), but as soon as the vehicle moves to a different orientation, the CFrame becomes incorrect (image 2). It looks like it’s an issue of object space vs. world space, but I’ve tried converting aimCFrame both ways at every step of the process and it’s only flipped the polarity I have to turn the axes. It hasn’t actually solved the issue of the welds’ orientations being based on the vehicle being at standstill.


CLIENT SCRIPT:

character.Humanoid.Seated:Connect(function(active, seatPart)
	--...
		--...
		elseif seatPart.Name == "TurretSeat" then
			local vehicle = seatPart.Parent
			local turretBarrel = vehicle.TurretBarrel
			local turretBody = vehicle.TurretBody
			
			local aimer = turretBarrel.Aimer
			weldUpDown = turretBody.WeldUpDown
			weldLeftRight = turretBody.WeldLeftRight
			
			local offsetUD = weldUpDown.C0
			local offsetLR = weldLeftRight.C0
			
			local seats = getSeats(vehicle:GetChildren())
			queryVehicleInfo:FireServer(vehicle)
			
			inputStart = UIS.InputBegan:Connect(function(input, GPE)
				if GPE then return end
				if input.UserInputType == Enum.UserInputType.MouseButton1 and firing == false then
					firing = true
					while firing do
						fire(vehicle, FIRE_SPEED, FIRE_RANGE, getOccupants(seats))
						fireTurret:FireServer(vehicle)
						task.wait(1 / FIRE_RATE)
					end
				end
			end)

			inputEnd = UIS.InputEnded:Connect(function(input, GPE)
				if GPE then return end
				if input.UserInputType == Enum.UserInputType.MouseButton1 then
					firing = false
				end
			end)

			sitConnection = run.RenderStepped:Connect(function()
				local aimCFrame
				local mousePosition = UIS:GetMouseLocation()
				local mouseRay = workspace.Camera:ViewportPointToRay(mousePosition.X, mousePosition.Y)
				local raycastParams = RaycastParams.new()
				raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
				raycastParams.FilterDescendantsInstances = {vehicle, getOccupants(seats)}
				local raycastResult = workspace:Raycast(mouseRay.Origin, mouseRay.Direction * 1000, raycastParams)
				if raycastResult then
					aimCFrame = CFrame.lookAt(aimer.WorldPosition, raycastResult.Position)
				else
					aimCFrame = CFrame.new(Vector3.new(0, 0, 0), mouseRay.Direction)
				end
				local x, y, z = aimCFrame:ToEulerAnglesYXZ()
				weldUpDown.C0 = offsetUD * CFrame.Angles(-x, 0, 0)
				weldLeftRight.C0 = offsetLR * CFrame.Angles(0, y, 0)
				aimTurret:FireServer(vehicle, weldUpDown.C0, weldLeftRight.C0)
			end)
		end
	end
end)
  • Since the turret now uses welds, both movement and firing are now done on the client side via remote events. However, it’s tough to properly record bullet hits because the bullet gets copied to all clients, causing multiple hits to be registered depending on the number of players. For example, a bullet that deals 50 damage server-side, with 2 players, would deal 100 damage, with 4 players 200 damage, etc. One idea I had for fixing this was to track the number of clients that a specific bullet registered hits on and only deal damage if it’s above a specific number, but I’m told this would create thousands of problems regarding different pings and StreamingEnabled. To me, the obvious, somewhat lazy solution would be only to register the bullet hit by the player who fired it, but I imagine this could be easily exploited. I would appreciate any pointers in finding a solution here; even if they’re completely abstract, I could probably translate them to code.

Please let me know if I need to provide any more information.

1 Like

I set up a little bit of a showcase to demonstrate more clearly why I think the first problem has to do with object space vs. world space:

In this first image, I’ve backed the truck up (without turning) from its original spawn point to have it sit in between two colored blocks. I then sat in the turret seat and aimed at the red block. The turret aims directly at it, as expected.

In this next image, I turned the truck around to face the opposite direction and positioned it between the two blocks again. This time, when I aim at the red block, the turret aims next to the blue block, in what appears to be the complete opposite direction of the red block relative to the turret.

Finally, I reoriented the truck to face the direction it was originally facing (for the most part) and positioned it one last time between the two blocks. Aiming at the red block, the turret has returned to normal, mostly.

As I mentioned in the first post, I’ve already tried converting aimCFrame to both object space and world space at every step in the process and it hasn’t solved the problem. Is there something I’m missing? Or is this not even an issue with object space vs. world space?

I seriously need help with this. I’ve tried everything that I can think of and nothing’s worked.

Have you tried using an HingeConstraint for the turret? Or are you already using it?

Using a hinge constraint would prevent me from being able to have another person drive the vehicle while someone is manning the turret. That was the first thing I tried before I started using welds.

How would that block you from doing that? I don’t understand sorry :sweat_smile:

This topic explains it well. You can’t split network ownership of an assembly between people; one person can have complete ownership and that’s it. That means in order to allow for more than one person to control the behavior of an assembly, you need to make a workaround. This workaround involves manipulating the orientations of two welds and then replicating that across all other clients through remote events.

Have you tried making the turret be a completely different veichle of which you control the orientation?
Or are you already doing taht

Also another idea, have you tried doing that and using PhisicsConstraints to keep them togheter?

1 Like

It doesn’t matter if the parts of the turret and the parts of the rest of the vehicle are in separate models. Network ownership applies to assemblies. If any part of them is connected in any way (which they’re going to be because they’re ultimately different parts of the same vehicle) via constraints or other means, then network ownership of both the turret and the vehicle is going to belong to the same person. There’s no way to split it between different people because only one person’s inputs can replicate to the server. I don’t know what “PhisicsConstraints” are, they don’t appear in documentation. If you’re talking about Mechanical Constraints, I already tried making the turret use hinges and that not working led me to where I am right now.

Okay, I don’t know how to help you then, also look for PhisicalConstraints there’s a topic about this in the #updates section

I’m still completely stuck and desperately need some insight. I recently tried adding the orientation of the vehicle’s body to the angles I get out of :ToEulerAnglesYXZ() and it made no difference and might’ve broken things even further. I had to add 180 degrees to the X value to even get it to aim in the right direction when situated at the origin.

-- the vehicle/turret driving function, what runs whenever someone sits down
character.Humanoid.Seated:Connect(function(active, seatPart)
	--...
		--...
		-- turret portion	
		elseif seatPart.Name == "TurretSeat" then
			local vehicle = seatPart.Parent
			local turretBarrel = vehicle.TurretBarrel
			local turretBody = vehicle.TurretBody
			
			local aimer = turretBarrel.Aimer
			weldUpDown = turretBody.WeldUpDown
			weldLeftRight = turretBody.WeldLeftRight
			
			local offsetUD = weldUpDown.C0
			local offsetLR = weldLeftRight.C0
			
			local seats = getSeats(vehicle:GetChildren())
			queryVehicleInfo:FireServer(vehicle)
			-- input starts (shooting)
			inputStart = UIS.InputBegan:Connect(function(input, GPE)
				if GPE then return end
				if input.UserInputType == Enum.UserInputType.MouseButton1 then
					firing = true
					while firing do
						if tick() - (1 / FIRE_RATE) >= lastShot then
							local aimCFrame
							local mousePosition = UIS:GetMouseLocation()
							local mouseRay = workspace.Camera:ViewportPointToRay(mousePosition.X, mousePosition.Y)
							local raycastParams = RaycastParams.new()
							raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
							raycastParams.FilterDescendantsInstances = {vehicle, getOccupants(seats)}
							local raycastResult = workspace:Raycast(mouseRay.Origin, mouseRay.Direction * 1000, raycastParams)
							if raycastResult then
								aimCFrame = CFrame.lookAt(aimer.WorldPosition, raycastResult.Position)
							else
								aimCFrame = CFrame.new(Vector3.new(0, 0, 0), mouseRay.Direction)
							end
							local x, y, z = aimCFrame:ToEulerAnglesYXZ()
							x -= vehicle.PrimaryPart.Orientation.X + math.rad(180)
							y -= vehicle.PrimaryPart.Orientation.Y
							weldUpDown.C0 = offsetUD * (CFrame.Angles(x, 0, 0)):ToObjectSpace()
							weldLeftRight.C0 = offsetLR * (CFrame.Angles(0, -y, 0)):ToObjectSpace()
							aimTurret:FireServer(vehicle, weldUpDown.C0, weldLeftRight.C0)
							
							lastShot = tick()
							fire(vehicle, FIRE_SPEED, FIRE_RANGE, getOccupants(seats))
							fireTurret:FireServer(vehicle)
						end
						task.wait()
					end
				end
			end)
			-- input ends (shooting)
			inputEnd = UIS.InputEnded:Connect(function(input, GPE)
				if GPE then return end
				if input.UserInputType == Enum.UserInputType.MouseButton1 then
					firing = false
				end
			end)
		end
	end
end)

Here is a reference of how I did C0 manipulation for my turret module:

Three questions:

  1. Does it matter which BasePart I use for the first and fifth steps?
  2. Does :ToOrientation() replace :ToEulerAnglesYXZ() in my script?
  3. Why clamp the orientation? What does that do?

EDIT: The method you outlined turned out to be the solution (I didn’t clamp anything because I think that function is unique to your module) but I’d still like answers on these just in case anyone else is having the same problem.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.