Strange Client-Side Vehicle Behavior When Touched

I didn’t really know WHAT to call this, because I’ve never seen anything quite like it. I don’t know what causes it, all I know is that it’s client side (It only happens to the player who owns the vehicle). Basically, whenever this feels like happening (I can’t get it to consistently happen), the player who owns the vehicle can not move the vehicle anymore, but there’s no errors or anything in the console, and the script cant be completely busted because the “press E to sit” thing still works just fine and it’s part of the same event. Also, whenever the player is not in the vehicle and they touch said vehicle, the player and the vehicle can dissapear for a few moments, and when you reappear, your movement is super buggy and laggy for a couple seconds.

Here’s a video demonstrating what this bug does:

Server script:

local serverEvents = game.ServerStorage.Events
local clientEvents = game.ReplicatedStorage.Events
local vehicle = script.Parent
local owner = vehicle.Owner.Value or vehicle.Owner.Changed:Wait()
local fireRate = vehicle.Firerate.Value
local seats = vehicle.Seats
local vehicleSeat = seats.VehicleSeat
local base = vehicle.Base
local hullParts = {}
for _, part in pairs(vehicle:GetDescendants()) do
	if part.Name == "ColorPart" then
		table.insert(hullParts, part)
	end
end
local hullNumber = #hullParts
local maxHealth = vehicle.MaxHealth.Value
local health = vehicle.Health
local currentHealth = maxHealth
local smoke = vehicle.SmokePart.Smoke
local fire = vehicle.SmokePart.Fire
local rightTrack = vehicle.RightTrack
local leftTrack = vehicle.LeftTrack
local rightMotors = {}
local leftMotors = {}
local wheels = {}
local physicsService = game:GetService("PhysicsService")
local muzzle = vehicle.Turret.Gun.Muzzle
local maxTorque = vehicle.Torque.Value
local maxSpeed = vehicle.Speed.Value
local minDamage = vehicle.MinDamage.Value
local maxDamage = vehicle.MaxDamage.Value
local healthPerPart = math.ceil(maxHealth/hullNumber)
local tags = {}
local damagedParts = {}
local listeners = {}
local currentHealth = health.Value
local despawnTick = tick()
local running = false
local gunGyro = vehicle.Turret.Gun.Base.BodyGyro
local collisionGroup1 = "Hull"
local collisionGroup2 = "Wheels"
local loaded = vehicle.Loaded
gunGyro.CFrame = gunGyro.Parent.CFrame

for _, wheel in pairs(rightTrack:GetChildren()) do
	if wheel.Name == "Wheel" then
		table.insert(rightMotors, wheel.WheelAxle.HingeConstraint)
		table.insert(wheels, wheel.Wheel.Hitbox)	
	end
end

for _, wheel in pairs(leftTrack:GetChildren()) do
	if wheel.Name == "Wheel" then
		table.insert(leftMotors, wheel.WheelAxle.HingeConstraint)
		table.insert(wheels, wheel.Wheel.Hitbox)		
	end
end

for _, part in pairs(hullParts) do
	physicsService:SetPartCollisionGroup(part, collisionGroup1)
end

for _, part in pairs(wheels) do
	physicsService:SetPartCollisionGroup(part, collisionGroup2)
end

physicsService:CollisionGroupSetCollidable(collisionGroup1, collisionGroup2, false)

local function stop()
	for _, motor in pairs(rightMotors) do
		motor.AngularVelocity = 0
	end
	for _, motor in pairs(leftMotors) do
		motor.AngularVelocity = 0
	end
	base.Running:Stop()		
end

local function startUp()
	running = true	
	base.Running:Play()
end

health.Changed:Connect(function(new)
	local damageRatio = 1 - new/maxHealth		
	fire.Enabled = damageRatio >= 0.8
	smoke.Enabled = damageRatio >= 0.5
	smoke.Rate = damageRatio * 80 - 35
	smoke.Speed = NumberRange.new(damageRatio * 3, damageRatio * 5)
	
	if new < currentHealth then
		while (maxHealth - healthPerPart * #damagedParts) - new > healthPerPart do
			local part = hullParts[math.random(1, hullNumber)]
			if not table.find(damagedParts, part) then
				part.Material = Enum.Material.CorrodedMetal	
				table.insert(damagedParts, part)
			end
		end
	elseif new > currentHealth then
		while (maxHealth - healthPerPart * (#damagedParts - 1)) - new < healthPerPart do
			local part = hullParts[math.random(1, hullNumber)]
			local toRemove = table.find(damagedParts, part)
			if toRemove then
				part.Material = Enum.Material.Metal
				table.remove(damagedParts, toRemove)
			end
		end			
	end
	currentHealth = new

	if new <= 0 then
		stop()			
		for _, seat in pairs(seats:GetChildren()) do
			seat.Disabled = false
			seat.ProximityAttachment.ProximityPrompt.Enabled = false
			if seat.Occupant then
				seat.Occupant.Sit = false
			end
		end
		vehicle:BreakJoints()
		base.Explosion:Play()
		wait(2.5)
		if vehicle and vehicle.Parent then
			vehicle:Destroy()
			vehicle = nil
		end
	end
end)

vehicle.Fire.OnServerEvent:Connect(function(_, rayHit, position)
	if loaded.Value then
		loaded.Value = false
		clientEvents.TankFire:FireAllClients(owner, muzzle)
		serverEvents.TankFire:Fire(owner, rayHit, position)
		wait(4.5)
		loaded.Value = true
	end
end)

for _, seat in pairs(seats:GetChildren()) do
	seat:GetPropertyChangedSignal("Occupant"):Connect(function(new)
		if not seat.Occupant and vehicle and vehicle.Parent then
			seat.Disabled = true
			seat.ProximityAttachment.ProximityPrompt.Enabled = true
		end
	end)
	if seat == vehicleSeat then
		seat.ProximityAttachment.ProximityPrompt.Triggered:Connect(function(player)
			local humanoid = player.Character.Humanoid
			if ((seat == vehicleSeat and player == owner) or seat ~= vehicleSeat) and humanoid.Health > 0 and not humanoid.Sit then
				seat:Sit(player.Character.Humanoid)
				seat.Disabled = false
				seat.ProximityAttachment.ProximityPrompt.Enabled = false
				for _, part in pairs(vehicle:GetDescendants()) do
					if part:IsA("BasePart") then 
						part:SetNetworkOwner(owner)
					end
				end
				local localScript = script.LocalVehicleScript:Clone()
				localScript.Parent = player.PlayerGui
				localScript.Disabled = false
				local function shutdown()
					for _, part in pairs(vehicle:GetDescendants()) do
						if part:IsA("BasePart") then 
							part:SetNetworkOwnershipAuto()
						end
					end
					gunGyro.CFrame = gunGyro.Parent.CFrame
					despawnTick = tick()
					running = false
					for _, listener in pairs(listeners) do
						listener:Disconnect()
					end
					listeners = {}
					stop()
				end
				listeners[1] = humanoid.Seated:Connect(function(new)
					if not new then
						player.Character.HumanoidRootPart.CFrame = vehicleSeat.CFrame * CFrame.new(0, 13, 0)
						shutdown()
					end
				end)
				listeners[2] = humanoid.Died:Connect(shutdown)
				startUp()
			end
		end)
	else
		seat.ProximityAttachment.ProximityPrompt.Triggered:Connect(function(player)
			local humanoid = player.Character.Humanoid
			if ((seat == vehicleSeat and player == owner) or seat ~= vehicleSeat) and humanoid.Health > 0 and not humanoid.Sit then
				seat:Sit(humanoid)
				seat.Disabled = false
				seat.ProximityAttachment.ProximityPrompt.Enabled = false
			end
		end)			
	end
end

for _, motor in pairs(rightMotors) do
	motor.MotorMaxTorque = maxTorque
end

for _, motor in pairs(leftMotors) do
	motor.MotorMaxTorque = maxTorque
end

vehicle.AncestryChanged:Connect(function()
	if not vehicle:IsDescendantOf(game) then
		for _, listener in pairs(listeners) do
			listener:Disconnect()
		end
		listeners = nil
	end
end)

while vehicle:IsDescendantOf(game) do
	if not running and tick() - despawnTick	>= 300 then
		vehicle:Destroy()
		break
	end
	wait(0.5)
end

Local script:

local player = game.Players.LocalPlayer
local runService = game:GetService("RunService")
local contextActionService = game:GetService("ContextActionService")
local userInputService = game:GetService("UserInputService")
local character = player.Character
local humanoid = character:WaitForChild("Humanoid")
local vehicleSeat = humanoid.SeatPart
local vehicle = vehicleSeat:FindFirstAncestor("Vehicle")
local base = vehicle.Base
local health = vehicle.Health
local rightTrack = vehicle.RightTrack
local leftTrack = vehicle.LeftTrack
local rightMotors = {}
local leftMotors = {}
local turn = 0.2
local currentVelocity = 0
local mouse = player:GetMouse()
local camera = workspace.CurrentCamera
local gunBase = vehicle.Turret.Gun.Base
local gunGyro = gunBase.BodyGyro
local muzzle = vehicle.Turret.Gun.Muzzle
local enabled = true
local down = false
local mobile = false
local fireRate = vehicle.Firerate.Value
local jumpButton = player:FindFirstChild("JumpButton", true)
local crosshair = game.ReplicatedStorage.Assets.VehicleCrosshair:Clone()
local mobileCrosshair = vehicle.MobileCrosshair:Clone()
local maxSpeed = vehicle.Speed.Value
local tireRadius = vehicle.TireRadius.Value
local realigning = false
local alignTimer = tick()
local alignTime = 2
local vehicleFilter = RaycastParams.new(); vehicleFilter.FilterType = Enum.RaycastFilterType.Whitelist; vehicleFilter.FilterDescendantsInstances = {workspace.Terrain}
local gunFilter = RaycastParams.new(); gunFilter.FilterType = Enum.RaycastFilterType.Blacklist; gunFilter.FilterDescendantsInstances = {vehicle, character, workspace.Barrier}
local loaded = vehicle.Loaded

camera.CameraSubject = vehicle.CameraPart
mouse.Icon = "rbxassetid://6041882539"
crosshair.Parent = player:FindFirstChild("GUI", true)

for _, wheel in pairs(rightTrack:GetChildren()) do
	table.insert(rightMotors, wheel:FindFirstChildWhichIsA("HingeConstraint", true))
end

for _, wheel in pairs(leftTrack:GetChildren()) do
	table.insert(leftMotors, wheel:FindFirstChildWhichIsA("HingeConstraint", true))
end

local function setMotorVelocity()
	local rightVelocity = -currentVelocity
	local leftVelocity = currentVelocity
	
	if vehicleSeat.SteerFloat ~= 0 then
		rightVelocity = rightVelocity + maxSpeed * vehicleSeat.SteerFloat * (vehicleSeat.ThrottleFloat < 0 and -turn or turn)
		leftVelocity = leftVelocity + maxSpeed * vehicleSeat.SteerFloat * (vehicleSeat.ThrottleFloat < 0 and -turn or turn)
	end
	
	for _, motor in pairs(rightMotors) do
		motor.AngularVelocity = rightVelocity
	end
	
	for _, motor in pairs(leftMotors) do
		motor.AngularVelocity = leftVelocity
	end	
end

vehicleSeat:GetPropertyChangedSignal("ThrottleFloat"):Connect(function()
	if health.Value > 0 and not realigning then
		currentVelocity = vehicleSeat.ThrottleFloat ~= 0 and maxSpeed * vehicleSeat.ThrottleFloat / tireRadius or 0
		setMotorVelocity()
	end	
end)

vehicleSeat:GetPropertyChangedSignal("SteerFloat"):Connect(function()
	if health.Value > 0 and not realigning then
		setMotorVelocity()
	end	
end)

contextActionService:BindAction("Fire", function(_, inputState)
	if inputState == Enum.UserInputState.Begin then
		down = true
		if enabled and down and humanoid.Health > 0 then
			if not loaded.Value then
				loaded.Changed:Wait()
			end
			enabled = false
			while down and humanoid.Health > 0 do
				game.ReplicatedStorage.Events.LocalSound:Fire(nil, "TankFire", muzzle.Position)
				muzzle.FireEffect1:Emit(100)
				muzzle.FireEffect2:Emit(100)
				muzzle.SmokeEffect:Emit(100)
				local result = workspace:Raycast(muzzle.Position, muzzle.CFrame.RightVector * -5000, gunFilter)
				if result then
					vehicle.Fire:FireServer(result.Instance, result.Position)
				end
				coroutine.resume(coroutine.create(function()
					muzzle.Recoil.Force = muzzle.CFrame.RightVector * 1500000
					wait()
					muzzle.Recoil.Force = Vector3.new(0, 0, 0)
				end))
				if not loaded.Changed:Wait() then
					loaded.Changed:Wait()
				end
			end
			enabled = true
		end
	else
		down = false
	end
end, true, Enum.UserInputType.MouseButton1, Enum.KeyCode.ButtonR1)

contextActionService:SetTitle("Fire", "Fire")

if jumpButton and userInputService.TouchEnabled and not userInputService.MouseEnabled then
	mobile = true
	local fireButton = contextActionService:GetButton("Fire")
	local paddingX = math.abs(jumpButton.Position.X.Offset) - jumpButton.AbsoluteSize.X
	local paddingY = math.abs(jumpButton.Position.Y.Offset) - jumpButton.AbsoluteSize.Y
	fireButton.ActionTitle.Font = Enum.Font.TitilliumWeb
	fireButton.ActionTitle.TextStrokeTransparency = 0.4
	fireButton.Size = jumpButton.Size
	fireButton.ActionTitle.TextSize = fireButton.AbsoluteSize.Y * 0.4
	fireButton.Position = UDim2.new(1, -(paddingX + fireButton.AbsoluteSize.X), 1, -(paddingY + jumpButton.AbsoluteSize.Y + fireButton.AbsoluteSize.Y + 10))
	mobileCrosshair.Parent = player:FindFirstChild("GUI", true)	
end

runService:BindToRenderStep("Aim", 100, function()
	local endPosition = mouse.hit.p
	if mobile then
		local unitRay = camera:ScreenPointToRay(crosshair.AbsolutePosition.X + crosshair.AbsoluteSize.X/2, crosshair.AbsolutePosition.Y + crosshair.AbsoluteSize.Y/2)
		local hit, hitPosition = workspace:FindPartOnRay(Ray.new(unitRay.Origin, unitRay.Direction * 5000), character)
		endPosition = hitPosition
	end
	gunGyro.CFrame = CFrame.new(gunBase.Position, endPosition)
	local result = workspace:Raycast(muzzle.Position, muzzle.CFrame.RightVector * -5000, gunFilter)
	if result then
		local vector, onScreen = camera:WorldToViewportPoint(result.Position)
		crosshair.Visible = onScreen
		crosshair.Position = UDim2.fromOffset(vector.X, vector.Y)
	end
end)

local function disableTank()
	mouse.Icon = ""
	crosshair:Destroy()
	mobileCrosshair:Destroy()
	camera.CameraSubject = humanoid
	runService:UnbindFromRenderStep("Aim")
	contextActionService:UnbindAction("Fire")
	game.ReplicatedStorage.Events.RemoveVehicleScript:FireServer()
	script.Disabled = true	
end

humanoid.Seated:Connect(function(sitting, seat)
	if not sitting then
		disableTank()
	end
end)

humanoid.Died:connect(function()
	disableTank()
end)

while health.Value > 0 do
	local result = workspace:Raycast(base.Position, base.CFrame.UpVector * -30, vehicleFilter)
	if not result then
		realigning = true
		setMotorVelocity()
		local bodyGyro = Instance.new("BodyGyro")
		bodyGyro.CFrame = CFrame.new(0, 0, 0)
		bodyGyro.MaxTorque = Vector3.new(0, 0, math.huge)
		bodyGyro.P = 5000
		bodyGyro.Parent = base
		wait(2)
		bodyGyro:Destroy()
		realigning = false
	end
	wait(1)
end

Any help with this would be greatly appreciated, as I am completely stumped as to what could be causing this behavior. Thanks!

1 Like

It looks like a NAN error similar to this other tank problem.

And my one:

I would suggest looking into all CFrame math operations, Vector3 stuff, and importantly humanoid related scripts for the issue.

1 Like

I’m not using any humanoid or vector3 stuff, and I think i’ve isoloated the problem to this line:

gunGyro.CFrame = CFrame.new(gunBase.Position, endPosition)

Which is responsible for updating the turret gyro to face the player’s mouse. With this line commented out, the glitch never seems to occur. That being said, I’m not quite sure what would be causing it to become NaN, or how to fix it. Any suggestions?

1 Like

Yeah, there is a possibility for endPosition to equal gunBase.Position which will break the CFrame and possibly cause an error. (CFrame looks at itself)

Try detecting this scenario via Magnitude distance between gunbase post and end position and replace it with another CFrame or stop updating this CFrame.

1 Like

I tried detecting the distance to only update the CFrame if the magnitude is > 1, and it still happened. And just to test the positions being equal to eachother, I tried CFrame.new(Vector3.new(0, 0, 0), Vector3.new(0, 0, 0)) which didn’t break it, it just caused the turret to spin out of control incredibly fast. I also tried plugging in into the Vector3.new() just to see what would happen if it had a NaN input, and it just locked the gyro to a certain position. I can’t seem to break this thing when I want to.

1 Like