Why is my tower placement system breaking?

I’ve been working on Bloxy Hero Defense (My current project) for a while, and (as the title states) my placement system is breaking.

I’m a bit new to raycasting, so please notify me in the replies if you see anything wrong with it. :smiley:

Placement system (Server):

remotes.isPlacingEvent.OnServerEvent:Connect(function(plr, x, z, y, mouseOrigin)
		local blackList = {}
		local tower = toPlace:GetChildren()
		local primaryPart
		for i, part in ipairs(tower) do
			if part.Name == "Tower" or part.Parent.PrimaryPart == part then primaryPart = part end
			table.insert(blackList, part)
		end
		for i, v in ipairs(game.Players:GetPlayers()) do
			local char = v.Character or v.CharacterAdded:Wait()
			for j, part in ipairs(char:GetChildren()) do
				if part:IsA("BasePart") then
					table.insert(blackList, part)
				end
			end
		end
		local raycastParams = RaycastParams.new()
		raycastParams.FilterDescendantsInstances = blackList
		raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
		raycastParams.IgnoreWater = true
		local rayCast2 = game.Workspace:Raycast(mouseOrigin, Vector3.new(0, -10, 0), raycastParams)
		local raycastResult2
		if rayCast2 ~= nil then
			print(rayCast2)
			raycastResult2 = rayCast2.Instance
		elseif rayCast2 == nil then
			raycastResult2 = game.ServerStorage.BaseplateCopy
		end
		
		if raycastResult2.Parent == game.Workspace.AllowedToPlaceOn then
			for i, part in ipairs(toPlace:GetChildren()) do
				if hasProperty(part, "UsePartColor") then
					part.UsePartColor = false
				elseif hasProperty(part, "BrickColor") then
					part.BrickColor = rangeColor
				else
					continue
				end
			end
			ableToPlace = true
			remotes.SendPlacementStatus:FireClient(plr, ableToPlace)
		else
			print(raycastResult2)
			for i, part in ipairs(toPlace:GetChildren()) do
				if hasProperty(part, "UsePartColor") then
					part.UsePartColor = true
				elseif hasProperty(part, "BrickColor") then
					part.BrickColor = BrickColor.new("Really red")
				else
					continue
				end
			end
			print("You are not allowed to place here")
			ableToPlace = false
			remotes.SendPlacementStatus:FireClient(plr, ableToPlace)
		end
		placeFunction(toPlace, x, z, raycastResult2, y)
	end)

Local script:

local repStorage = game:GetService("ReplicatedStorage")
local remotes = require(repStorage:WaitForChild("RemoteModule"))
local runService = game:GetService("RunService")
local isPlacing = false
local UserInputService = game:GetService("UserInputService")
local plrService = game:GetService("Players")
local PlaceConnection
local RotateConnection
local currentTowerBeingPlaced = nil
local plr = plrService.LocalPlayer
local rotateDebounce = false
local tweenService = game:GetService("TweenService")
local ableToPlace
local tweenInfo = TweenInfo.new(0.5, Enum.EasingStyle.Quint, Enum.EasingDirection.Out, 0, false)
script.Parent.MouseButton1Up:Connect(function()
	remotes.SendPlacementStatus.OnClientEvent:Connect(function(status)
		ableToPlace = status
	end)
	print("Clicked!")
	print(isPlacing)
	if isPlacing == false then
		isPlacing = true
		local mouse = plr:GetMouse()
		local tower = repStorage:WaitForChild(script.Parent.Name)
		remotes.startPlacing:FireServer(tower)
		remotes.SendTower.OnClientEvent:Connect(function(towerE)
			mouse.TargetFilter = towerE
			currentTowerBeingPlaced = towerE
			print(currentTowerBeingPlaced)
		end)
		print("Fired from client!")
		PlaceConnection = runService.RenderStepped:Connect(function()
			remotes.isPlacingEvent:FireServer(mouse.Hit.Position.X, mouse.Hit.Position.Z, mouse.Hit.Position.Y, mouse.UnitRay.Origin)
		end)
		remotes.SetTransparencyToOtherClients.OnClientEvent:Connect(function(transparency, towerE)
			for i, part in ipairs(towerE:GetChildren()) do
				part.Transparency = transparency
			end
		end)
		RotateConnection = UserInputService.InputBegan:Connect(function(input, gpu)
			if gpu then return end
			if input.KeyCode == Enum.KeyCode.R then
				if not rotateDebounce then
					rotateDebounce = true
					print(currentTowerBeingPlaced)
					remotes.RotateOnServer:FireServer()
					local tween = tweenService:Create(currentTowerBeingPlaced.PrimaryPart, tweenInfo, {Orientation = currentTowerBeingPlaced.PrimaryPart.Orientation + Vector3.new(0, 90, 0)})
					tween:Play()
					tween.Completed:Wait()
					rotateDebounce = false
				end
			end
		end)
		local downConnection = mouse.Button1Down:Connect(function()
			if ableToPlace then
				remotes.place:FireServer()
				RotateConnection:Disconnect()
				PlaceConnection:Disconnect()
				isPlacing = false
			else
				print("Cannot place tower")
			end
		end)
	end
end)

You sure do love posting this thread. If it’s breaking do you mean erroring or just not working as you would expect?

When I start placing a test tower, it seems to work for about 3 seconds before it turns to red color (Not allowed to place tower)

Dude I posted this thread just once 5 hours earlier, chill

There can be a lot changed here. Why are you raycasting on the server? You realize you can just do that on the client and then give the server the correct coordinates? Also you should be doing everything visually on the client so it gives the server less work.

remotes.SendPlacementStatus.OnClientEvent:Connect(function(status)
    ableToPlace = status
end)

Also lol, what is this? Elaborate on why you are using this.
Reason I said all that is because it helps debug issues like this. Formatted/organized/efficient code helps with that.

Also you don’t really explain much on what the issue is. Mind elaborating on that too?

I’m not a good scripter, so please forgive me if I have bad coding practices.

I suck (My own words) at scripting, and I don’t know any other way to send variables through scripting (I just thought of module scripts as a sort of middle-man rn, not sure if it’ll work though.)

Alright, here’s what happens when I try to place a tower:

  1. (3 seconds before issue) I click the button that allows me to place a tower. Everything works okay.
  2. (Issue) Tower turns red, indicating that I’m not allowed to place the tower. Doing some printing showed me that baseplate seems to be nil at this point (Yet I see it right below me)

Raycasting on the server prevents the exploit (if OP is using raycasting to detect if anything is being hit) of parts being spammed into 1 position.

Another issue is you keep triggering your SendPlacementStatus connection every time the button is pressed. You are constantly creating more and more connections… and I was writing this I just found out your issue.

You are creating insane amounts of connections every time the button is pressed. These connections are causing major client desyncs that start slow but end off high. You need to rewrite and properly organize your code as I can’t understand what goes where.

1 Like

Any shot that you are cloning/creating the baseplate on the client? This code is a mess so I can’t tell.

No, I only did a few tower defense systems (Enemy, placement system) and I’m sure no other scripts affect the baseplate in any way (because the entire project is just those 2 systems as of now)

Plus the baseplate is automatically created in the template

EDIT:

This issue happens when I place the tower for the 1st time.

Then how could the baseplate be nil unless of course that’s not the part that your code refers to?

Please double-check that your code is properly referring to whatever part you are saying is nil.

You don’t need to be ray-casting on the server as it does not make any sort of difference. To prevent parts being spammed into one position you can attempt implementing something like a cool down and you can save the coordinates the player has previously placed to verify it’s a legitimate position, as-well as having any other checks.

Something like this:

function BuildUtil.GetMousePosition(IgnoreList)
	local NewRay = Ray.new(Camera.CFrame.Position, (Mouse.Hit.Position - Camera.CFrame.Position).Unit * 300);
	local _, Position, SurfaceNormal = workspace:FindPartOnRayWithIgnoreList(NewRay, IgnoreList);
	local EditedPosition = Position + SurfaceNormal;

	return EditedPosition.X, EditedPosition.Y, EditedPosition.Z
end

You can use a method like that in some utility module to get X, Y, Z coordinates from the mouse hit. You can then send those coordinates to the server and the server can go through multiple checks and whatever you need before placing it. (You can probably use Workspace:Raycast() instead) One of the main things involved in those checks Is the os.time() to check they are not constantly spamming the remote, as-well as doing that on the client. As far as I know, exploits don’t really matter when it comes to ray-casting on the server rather than the client? Maybe I’m wrong…?

You can also put a whitelist in the RaycastParams and only set it to some sort of build-plot, which saves you from having to check the result if it hit a Baseplate.

In a game I’m currently working on, it uses a system like this, you can check the game out: Monster Blast! - Roblox

You definitely don’t need a “SendPlacementStatus” remote to tell the client if they’re able to place, as you are able to do those types of checks on the client.

(Let me note that you can actually place the block on the client first before you fire the remote to tell the server you want to place a block, and then destroy that block in 1-2 seconds. This helps make network lag look like nothing with placement systems)

(The logic I’ve just explained is all used in the game linked)

1 Like

Note that most of the work for placement systems is the actual dragging of the object. You want it to be updated wherever you hover, and then when you click send those coordinates to the server to verify and eventually place. Logic really comes into play here, brainstorming of the system.

You can see that in my game it actually uses a spring library to move around the model. The system has not experienced any issues with exploiters to my knowledge and has not had any issues lagging/network lag is not visible.

(Sorry I keep editing, I just keep thinking of things) (Using a whitelist for the rays is really good with these types of things because it can limit your ray’s vision to ONLY the specified whitelist)

My honest suggestion is that you start this system from scratch and just read up on tutorials & check out some of the advice I mention above. I know there are a couple really good tutorials on this stuff on the forums. Unfortunately most of them don’t really account for any kind of lag though.

1 Like

Alright, I’ll keep my current system in a folder and redo my 2 days of (broken) work. I’m not really looking for performance boosts though, this is just a little project, and I don’t have any current plans for making it a game.

My current system isn’t even lagging a bit. I’ll set an FPS counter and see how bad performance is. If it isn’t that laggy, I might consider fixing my current code. (I mean I did fix it myself a bunch of times, I’ll take your solutions for both of these options and see if they work.)

1 Like

Alright. Can you send me a GIF of the current issue? Just so I can visualize the issue at hand.

You can also attempt to make the whitelist solely the baseplate so there is ensured no issues with it being nil or whatever.

local raycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = {workspace.BuildPlot}
raycastParams.FilterType = Enum.RaycastFilterType.Whitelist
raycastParams.IgnoreWater = true

local rayCast = game.Workspace:Raycast(mouseOrigin, Vector3.new(0, -10, 0), raycastParams)
1 Like

Just took a video of it, and I was actually a bit surprised to see it was allowed to place for about .5 seconds before returning to it’s usual state.

Video:

Sorry about the video quality

I just realized, why are you raycasting 10 studs down from the mouse origin?

1 Like

Honestly have no idea, like I said I suck at scripting (Not really though)

Well you are basically taking the origin of the mouse and raycasting all the way under the baseplate LOL.

PlaceConnection = runService.RenderStepped:Connect(function()
	remotes.isPlacingEvent:FireServer(mouse.Hit.Position.X, mouse.Hit.Position.Z, mouse.Hit.Position.Y, mouse.UnitRay.Origin)
end)

This is also a HUGE issue. You are firing a remote every frame.
You realize you can just hook the connection up to a “Mouse.Move”?

There’s not really much I can do in terms of helping other than guiding you onto making the code better – the type of system you have right now is pretty messed up

Consider making the Mouse.Move and then raycasting out and positioning a “Ghost” model of the current model you have, then when the user wants to request a placement, send it to the server to make the placement and do some checks.

1 Like

Thank you for your help. I haven’t solved my problem yet, but I still wonder why is the baseplate nil? No other scripts affect the baseplate in any way, so I’m pretty confused.

EDIT: So the problem is not the baseplate, but the raycast itself. What’s wrong here?

1 Like

Haha. You are raycasting 10 studs down :rofl:
Either that, or it’s because there is something wrong with the blacklist you provided and it’s getting confused.

I changed the blacklist to a whitelist, included the baseplate, and made it raycast 1 stud down instead. I still don’t understand anything.