Cannon System Broken - Works Kind of in Studio, Completely Busted in-game

In one of my games, there is a cannon feature where players can, pick a player they want to launch a cannon ball at that is in-game, and a cannon will be launched at the chosen player, and they will lose a life.

However, this feature has become broken completely recently, as seen below. Nothing happens!

It’s supposed to function like seen in this video, but after a few uses, the cannon ball just randomly shoots out into the void, not hitting anything. This video was taken in studio, not in-game.

Features Broken:

  • In regular games, buying the developer product for the cannon will not put you in the seat for it, and therefore also will not display the necessary targeting UI
  • In studio, a few shots of the cannon will result in it not hitting anything, and aiming at totally something random.

I need to get this fixed ASAP. Below is the code:

---- server sided code

--- below is the projectile module code:
local ball = {}

local CFrame_New = CFrame.new
local Vector3_New = Vector3.new

local T = 1;

local Gravity = Vector3_New(0, -workspace.Gravity, 0)

function ball:Create(RootPart:BasePart,Target:BasePart)
	local X0 = RootPart.CFrame * Vector3_New(0, 0, 0)

	-- calculate the v0 needed to reach mouse.Hit.p
	local V0 = (Target.Position - X0 - 0.5*Gravity*T*T)/T;

	-- have the ball travel that path
	local ball = Instance.new("Part")
	ball.Material = Enum.Material.Neon
	ball.BrickColor = BrickColor.new("Black")
	ball.Shape = Enum.PartType.Ball
	ball.Velocity = V0;
	ball.CanCollide = false;
	ball.CFrame = CFrame_New(X0)
	ball.Parent = workspace
	
	return ball
end

return ball

--- below is the code for the cannon script
local currentGunner = nil
local projectile = require(scripts.Modules.projectile)
local rootPart = cannon.Cannon.Barrel.LaunchOrigin

local function calculateServoAngle(hinge,target)
	local Turn,Tilt,Roll = hinge.Attachment1.WorldCFrame:ToObjectSpace(CFrame.new(hinge.Attachment0.WorldPosition, target.Position)):ToOrientation()
	return math.deg(Turn)-90
end

cannon.Controls.control.ProximityPrompt.Triggered:Connect(function(player: Player) 
	if gameInfo:GetAttribute("playing") == true then
		market:PromptProductPurchase(player,menu.cannon)
		local marketConnect
		marketConnect = market.PromptProductPurchaseFinished:Once(function(userId: number, productId: number, isPurchased: boolean)  
			if userId == player.UserId and productId == menu.cannon and isPurchased == true and not cannon.Controls.control.Occupant then
				currentGunner = player
				local char = player.Character or player.CharacterAdded:Wait()
				local hum = char:FindFirstChildOfClass("Humanoid")
				cannon.Controls.control:Sit(hum)
				clientRemotes.cannonToggle:FireClient(player,true)
				cannon.Controls.control.ProximityPrompt.Enabled = false
				task.wait(1)
				cannon.Controls.control:GetPropertyChangedSignal("Occupant"):Once(function()
					if not cannon.Controls.control.Occupant then
						print("byebyebye")
						clientRemotes.cannonToggle:FireClient(player,false)
						currentGunner = nil
						cannon.Controls.control.ProximityPrompt.Enabled = true
					end
				end)
			end
		end)	
	else
		clientRemotes.notify:FireClient(player,"Cannon Error","You may not attempt to fire the cannon at this time!")
	end	
end)

serverRemotes.cannon.OnServerEvent:Connect(function(plr:Player,target:Player)
	if plr == currentGunner and plr.Team == teams.Lobby then
		target = Players:FindFirstChild(tostring(target)) -- make sure target is still in-game
		if target and target.Team == teams.Survivors then
			local char = target.Character or target.CharacterAdded:Wait()
			local hum = char:FindFirstChildOfClass("Humanoid")
			local toHit = char:FindFirstChild("Head")
			local hinge = cannon.Cannon.Base:FindFirstChildOfClass("HingeConstraint")
			if not toHit then clientRemotes.notify:FireClient(plr,"Cannon Failure","Invalid Target!") return end
			if hinge then
				local angle = calculateServoAngle(hinge,toHit)
				hinge.TargetAngle = angle
				task.wait(1)
			end
			local ball = projectile:Create(rootPart,toHit)
			sound.CannonLaunch:Play()
			debris:AddItem(ball,5)
			local char2 = plr.Character or plr.CharacterAdded:Wait()
			local hum2 = char2:FindFirstChildOfClass("Humanoid")
			hum2.Jump = true
			local soundClone = sound.CannonBallExplode:Clone()
			soundClone.Parent = ball
			--- disable the gui for this here.
			ball.Touched:Connect(function(part:BasePart)
				if part.Parent == char then
					local newExp = Instance.new("Explosion")
					newExp.Position = part.Position
					newExp.BlastPressure = 0
					newExp.BlastRadius = 5
					newExp.Parent = workspace
					debris:AddItem(newExp)
					hitPlr(target)
					ball:Destroy()
				end
			end)
		else
			clientRemotes.notify:FireClient(plr,"Cannon Failure","Invalid Target!")
		end
	else
		warn(tostring(plr).." is likely exploiting")
	end
end)

--- ui/client code

local rs = game:GetService("ReplicatedStorage")
local players = game:GetService("Players")
local sound = game:GetService("SoundService")
local player = players.LocalPlayer
local gui = script.Parent
local killColor = Color3.fromRGB(255, 0, 0)
local defaultColor = Color3.fromRGB(244, 136, 42)
local target = nil

local function clearPlayers()
	for _,i in pairs(gui.Background.ScrollingFrame:GetChildren()) do
		if i:IsA("TextButton") then
			i:Destroy()
		end
	end
end

local function changeColor(ignore: TextButton)
	for _,i in pairs(gui.Background.ScrollingFrame:GetChildren()) do
		if i:IsA("TextButton") and i ~= ignore then
			i.BackgroundColor3 = defaultColor
		end
	end
end

rs.clientRemotes.cannonToggle.OnClientEvent:Connect(function(val:BoolValue)
	if val == true then
		gui.Enabled = true
		for _,p in pairs(players:GetPlayers()) do
			if p ~= player and p.Team ~= game:GetService("Teams").Lobby then
				local clone = script.Player:Clone()
				clone.Name = p.Name
				clone.Username.Text = string.sub(p.DisplayName, 1, 20)
				clone.Parent = gui.Background.ScrollingFrame
				clone.MouseButton1Click:Connect(function()
					clone.BackgroundColor3 = killColor
					changeColor(clone)
					sound["Click/Touch"]:Play()
					target = p
				end)
			end
		end
	else
		gui.Enabled = false
		target = nil
		clearPlayers()
	end
end)

gui.Background.FireButton.MouseButton1Click:Connect(function()
	if target ~= nil and players:FindFirstChild(tostring(target)) then
		sound["Click/Touch"]:Play()
		rs.serverRemotes.cannon:FireServer(target)
		gui.Enabled = false
		target = nil
		clearPlayers()
	end
end)

How do I fix this? When the errors mentioned above occur, there is no output.

Did some testing to it, got it to print the targeted head’s position.

In this image, the first print was successful hit, the second one was where it went haywire, despite the target being the same…

I believe I fixed the issue(s) here. NetworkOwnership of the projectile was busted. I will observe this patch over the next few days, and see what happens.