Hit Detection Problems with Local Parts and Network Ownership

Okay so here’s the situation:
I have a module script that handles most functions for my game. For a while, only NPCs had guns and thus they were the only ones who could shoot but now we are adding the option for players to get guns too. I am aware that to prevent the infamous “hopping” that the bullet does when switching network ownership automatically, I have to manually set the network ownership to the client of the person who fired it.

However, our game has multiple maps and each map is seen by the server but it is moved to replicated storage on all of the clients when they join the game. This is to reduce lag from rendering while having the server-side NPCs being able to interact with the map they are in.

The problem:
When I set the network ownership of the bullet to the server, everything works fine and it detects when the bullet is hit.

However, when the bullet is set to the network ownership of the client, it only detects when it hits things that aren’t touched by the client. This means that the bullets only register a hit when it hits terrain, other NPCs, and other players.

When a player spawns at a map, it is cloned locally from ReplicatedStorage and put into a folder in Workspace. The bullets do not register hits when they hit objects in these folders but they do register them when the network ownership is the server.

I don’t think that my code is the issue but here it is anyway:

function module.shoot(gun, targetLocation, team, dmg, firer) --team 0 = light, 1 = dark
	local bolt = game.ReplicatedStorage.BlasterBolt:Clone()
	local teamInd = Instance.new("NumberValue")
	if not dmg == nil then
		bolt.Damage.Value = dmg
	end
	teamInd.Parent = bolt
	if  team == 0 then
		teamInd.Name = "LightSide"
		bolt.Trail.Color = lightColor
		bolt.Particles.Color = lightColor
		bolt.OutterMesh.BrickColor = BrickColor.new("Lapis")
	elseif team == 1 then
		teamInd.Name = "DarkSide"
		bolt.Trail.Color = darkColor
		bolt.Particles.Color = darkColor
		bolt.OutterMesh.BrickColor = BrickColor.new("Really red")
	end
	local sound = Instance.new("Sound", gun)
	if firer and firer.Name == "Separatist Tank" then
		sound.SoundId = "rbxassetid://314322896"
	else
		sound.SoundId = "rbxassetid://387095243"
	end
	sound.MaxDistance = 750
	sound.Parent = gun
	sound.Volume = 1.5
	if gun.Parent.Name == "B2" then
		sound.PlaybackSpeed = 1.25
	elseif gun.Parent.Name == "Chaingun" then
		sound.PlaybackSpeed = 0.75
	end
	if game.Players:GetPlayerFromCharacter(firer) then
		local playerHit = game.Players:GetPlayerFromCharacter(firer)
		bolt.BulletScript.Shooter.Value = playerHit
	else
		bolt.BulletScript.Shooter.Value = firer
	end
	sound:Play()
	bolt.BodyVelocity.Velocity = CFrame.new(gun.Position, targetLocation).LookVector*250
	bolt.Parent = workspace
	bolt.CFrame = gun.CFrame
	bolt.CFrame = CFrame.new(gun.Position, targetLocation)
	local startPos = gun.Position
	if game.Players:GetPlayerFromCharacter(firer) then
		local playerHit = game.Players:GetPlayerFromCharacter(firer)
		--bolt:SetNetworkOwner(playerHit)
	else
		bolt:SetNetworkOwner(nil)
	end
	
	bolt.Touched:Connect(function(hit)
			if bolt.Dud.Value == false and hit.Transparency ~= 1 and hit.Parent.ClassName ~= "Accessory" 
                and hit.Name ~= "BlasterBolt" and not hit.Parent:FindFirstChild("Cosmetic") and not 
                hit:IsDescendantOf(firer) then
				--print("Bullet hit "..hit.Name)
				local lookVector = CFrame.new(startPos, targetLocation).LookVector*4000
				local ray = Ray.new(startPos, lookVector)
				
				local usedPositions = {}
				local usedParts = {}
				local usedNormals = {}
				local usedMaterials = {}
				
				local lastPosition = nil
				local lastPart = nil
				local lastNormal = nil
				local lastMaterial = nil
				
				repeat
					local ignoreList = {firer}
					for i = 1, #usedParts do
						table.insert(ignoreList, usedParts[i])
					end
					lastPart, lastPosition, lastNormal, lastMaterial = 
                    workspace:FindPartOnRayWithIgnoreList(ray, ignoreList)
					table.insert(usedParts, lastPart)
					table.insert(usedPositions, lastPosition)
					table.insert(usedNormals, lastNormal)
					table.insert(usedMaterials, lastMaterial)
				until lastPart == nil
				for i = 1, #usedParts do
					if game.Players:GetPlayerFromCharacter(firer) then
						--print(usedParts[i].Name)
					end
					if usedParts[i] == hit then
						--print(firer.Name .." impacted " .. usedParts[i].Name)
						bolt.Dud.Value = true
						bulletImpact(usedParts[i], bolt, usedPositions[i], usedNormals[i], usedMaterials[i], firer)
					end
				end
			end
	end)
	module.debris(bolt,5)
end

Edit: here’s a video of the issue. The ship interior is made up of local parts and the big square in the middle is never affected by the client. As you can see, the bullets make their bullet holes/marks in the part that isn’t altered by the client but the hits don’t register at all with the client parts of the ship.

Contrast this with this next video where the network owner of the bullets is set to the server. Notice how it registers the hits this time.

2 Likes

Hmm, this is tricky. My guess is when the bullets are simulated on the client, it does not register for hits because the server is looking for when the bullet is hit, but the physics of the bullet are handled by the client.

I hope this makes sense

Yeah, I figured it out. I ended up just doing all hit detection on the client and so if it hits a wall, your own client registers the effects since it doesn’t matter if that’s on the server. However if it hits another person, for example, then the server gets involved to properly deal with it.

1 Like