[SOLVED] How can I make this non-exploitable?

Hello! I was wondering how I should go about making my tower placement system non-exploitable. It is currently all local making it very vulnerable. Here is the script:

local player = game.Players.LocalPlayer

local Mouse = player:GetMouse()

local PlayerGui = player:WaitForChild('PlayerGui')
local Gui = PlayerGui:WaitForChild('ScreenGui')
local SpawnButton = Gui:WaitForChild('TextButton')

local IsSpawning = false
local CanPlace = false
local CurrentDecoy = nil

local Rotation = 1

--//Create Decoy\\--

function CreateDecoy()
	
	local Towers = game:GetService('ReplicatedStorage'):WaitForChild('Towers')
	local GunnerUpgrades = Towers:WaitForChild('GunnerUpgrades')
	local TowerDecoy = GunnerUpgrades:WaitForChild('Gunman1'):Clone()
	
	TowerDecoy.Parent = workspace.Maps.Tutorial
	TowerDecoy:SetPrimaryPartCFrame(CFrame.new(Mouse.Hit.Position))
	
	return TowerDecoy
end

--//Mouse Move\\--

Mouse.Move:Connect(function()	
	if IsSpawning == false or CurrentDecoy == nil then return end
	
	local posX = Mouse.Hit.X
	local posY = Mouse.Hit.Y + 0.8
	local posZ = Mouse.Hit.Z
	
	CurrentDecoy:SetPrimaryPartCFrame(CFrame.new(Vector3.new(posX, posY, posZ)))
	
	CurrentDecoy:SetPrimaryPartCFrame(CFrame.new(Vector3.new(posX, posY, posZ))*CFrame.Angles(0,math.rad(90*Rotation),0))
	
	local overlapParams = OverlapParams.new()
	overlapParams.FilterType = Enum.RaycastFilterType.Blacklist 
	overlapParams.FilterDescendantsInstances = {workspace.Placeable, CurrentDecoy:GetChildren()}

	local touchingParts = workspace:GetPartsInPart(CurrentDecoy.Outline, overlapParams)

	for _, v in pairs(touchingParts) do
		if v.Name == 'Outline' then
			
			CanPlace = false
			CurrentDecoy.Outline.Color = Color3.fromRGB(255,0,0)
			return
		end
	end
	
	if (Mouse.Target ~= workspace.Placeable) then
		
		CanPlace = false
		CurrentDecoy.Outline.Color = Color3.fromRGB(255,0,0)		
	elseif (Mouse.Target == workspace.Placeable) then
		
		CanPlace = true
		CurrentDecoy.Outline.Color = Color3.fromRGB(68,255,26)		
	end
end)

--//Player Clicks Mouse\\--

Mouse.Button1Down:Connect(function()
	if IsSpawning == false or CurrentDecoy == nil or CanPlace == false then return end
	
	CurrentDecoy.Parent = workspace
	CurrentDecoy.Outline.Transparency = 1
	CurrentDecoy = nil
	SpawnButton.Visible = true
	IsSpawning = false
end)

--//Button Press\\--

SpawnButton.MouseButton1Click:Connect(function()
	if IsSpawning == false then
		
		SpawnButton.Visible = false
		IsSpawning = true
		CurrentDecoy = CreateDecoy()
		Mouse.TargetFilter = CurrentDecoy
	end
end)

--//Input Functions\\--

local UserInputService = game:GetService("UserInputService")

UserInputService.InputBegan:Connect(function(input, gameProcessedEvent)
	if input.KeyCode == Enum.KeyCode.R and not gameProcessedEvent then
		if IsSpawning == false or CurrentDecoy == nil then return end
		
		local modelCFrame = CurrentDecoy:GetPrimaryPartCFrame()

		CurrentDecoy:SetPrimaryPartCFrame(modelCFrame * CFrame.Angles(0, math.rad(90), 0))
		
		if Rotation == 1 then

			Rotation = 2
		elseif Rotation == 2 then

			Rotation = 3
		elseif Rotation == 3 then

			Rotation = 4
		elseif Rotation == 4 then

			Rotation = 1
		end
	end
end)

UserInputService.InputBegan:Connect(function(input, gameProcessedEvent)
	if input.KeyCode == Enum.KeyCode.X and not gameProcessedEvent then
		if IsSpawning == false or CurrentDecoy == nil then return end

		CurrentDecoy:Destroy()
		CurrentDecoy = nil
		IsSpawning = false
		SpawnButton.Visible = true
	end
end)

How should I go about sending the information I can safely to the server through remote events? I have tried this already but it didn’t work very well. Thank you for the help in advance!

2 Likes

Validate the request on the server. You should probably use a remotefunction to return if the placement was allowed or not.

Sorry it took so long for me top get back to you. Here is my new script(s). It says there is an error at the marked line, the error is “attempt to index nil with GetChildren”

--//Local Script\\--
Mouse.Button1Down:Connect(function()
	if IsSpawning == false or CurrentDecoy == nil or CanPlace == false then return end
	
	local Check = PlaceCheck:InvokeServer(CurrentDecoy) -- Error
	CurrentDecoy.Parent = workspace.Maps.Tutorial.Towers
	CurrentDecoy.Outline.Transparency = 1
	CurrentDecoy = nil
	SpawnButton.Visible = true
	IsSpawning = false
end)
--//Server Script\\--
local PlacementEvents = game:GetService('ReplicatedStorage'):WaitForChild('PlacementEvents')
local PlaceCheck = PlacementEvents:WaitForChild('PlaceCheck')

PlaceCheck.OnServerInvoke = function(player, CurrentDecoy)
	
	local overlapParams = OverlapParams.new()
	overlapParams.FilterType = Enum.RaycastFilterType.Blacklist 
	overlapParams.FilterDescendantsInstances = {workspace.Placeable, CurrentDecoy:GetChildren()}
	
	local touchingParts = workspace:GetPartsInPart(CurrentDecoy, overlapParams)

	for _, v in pairs(touchingParts) do
		if v.Name == 'Outline' then

			return false
		else
			
			return true
		end
	end
end
1 Like

The issue here is that you are creating CurrentDecoy on the client.
So when it’s sent to the server because it was created on the client it doesn’t exist for the server leading to the attempt to index nil with :GetChildren() error

I want the decoy to only show up on the client so is there anyway I could create it on the server and make it only visible to the player that created it?

Keep it how it is and it will only show up on that client

You’re gonna have to change whats passed in the RemoteFunction
like invoking the server with the Position, Rotation
And the server should clone that Instance and put it in that Position, Rotation

If I go about doing it this way, is there any possible way to check if what the Decoy is touching allowed or not. Example: Decoy is touching a tree (not allowed) Decoy is touching nothing (is allowed). I don’t think so because the server cant see the decoy.

Could the client change the position being sent to the server?

Ok, I think I understand now, here is my local script:

local player = game.Players.LocalPlayer

local PlacementEvents = game:GetService('ReplicatedStorage'):WaitForChild('PlacementEvents')
local CreateDecoyEvent = PlacementEvents:WaitForChild('CreateDecoyEvent')
local PlaceCheckPosition = PlacementEvents:WaitForChild('PlaceCheckPosition')
local SendCurrentDecoy = PlacementEvents:WaitForChild('SendCurrentDecoy')

local Mouse = player:GetMouse()

local PlayerGui = player:WaitForChild('PlayerGui')
local Gui = PlayerGui:WaitForChild('ScreenGui')
local SpawnButton = Gui:WaitForChild('TextButton')

local IsSpawning = false
local CanPlace = false
local CurrentDecoy = nil

local Rotation = 1

--//Mouse Move\\--

Mouse.Move:Connect(function()	
	if IsSpawning == false or CurrentDecoy == nil then return end
	
	local posX = Mouse.Hit.X
	local posY = Mouse.Hit.Y + 0.8
	local posZ = Mouse.Hit.Z
	
	CurrentDecoy:SetPrimaryPartCFrame(CFrame.new(Vector3.new(posX, posY, posZ)))
	
	CurrentDecoy:SetPrimaryPartCFrame(CFrame.new(Vector3.new(posX, posY, posZ))*CFrame.Angles(0,math.rad(90*Rotation),0))
	
	local overlapParams = OverlapParams.new()
	overlapParams.FilterType = Enum.RaycastFilterType.Blacklist 
	overlapParams.FilterDescendantsInstances = {workspace.Maps.Tutorial.Placeable, CurrentDecoy:GetChildren()}

	local touchingParts = workspace:GetPartsInPart(CurrentDecoy.Outline, overlapParams)

	for _, v in pairs(touchingParts) do
		if v.Name == 'Outline' then
			
			CanPlace = false
			CurrentDecoy.Outline.Color = Color3.fromRGB(255,0,0)
			return
		end
	end
	
	if (Mouse.Target ~= workspace.Maps.Tutorial.Placeable) then
		
		CanPlace = false
		CurrentDecoy.Outline.Color = Color3.fromRGB(255,0,0)		
	elseif (Mouse.Target == workspace.Maps.Tutorial.Placeable) then
		
		CanPlace = true
		CurrentDecoy.Outline.Color = Color3.fromRGB(68,255,26)		
	end
end)

--//Player Clicks Mouse\\--

Mouse.Button1Down:Connect(function()
	if IsSpawning == false or CurrentDecoy == nil or CanPlace == false then return end
	
	PlaceCheckPosition:FireServer(Mouse.Hit.Position, Rotation, 'Gunman1')
	
	CurrentDecoy:Destroy()
	CurrentDecoy = nil
	SpawnButton.Visible = true
	IsSpawning = false
end)

--//Button Press\\--

SpawnButton.MouseButton1Click:Connect(function()
	if IsSpawning == false then
		
		CreateDecoyEvent:FireServer(Mouse.Hit.Position, 'Gunman1')
		
		SpawnButton.Visible = false
		IsSpawning = true
	end
end)

--//Input Functions\\--

local UserInputService = game:GetService("UserInputService")

UserInputService.InputBegan:Connect(function(input, gameProcessedEvent)
	if input.KeyCode == Enum.KeyCode.R and not gameProcessedEvent then
		if IsSpawning == false or CurrentDecoy == nil then return end
		
		local modelCFrame = CurrentDecoy:GetPrimaryPartCFrame()

		CurrentDecoy:SetPrimaryPartCFrame(modelCFrame * CFrame.Angles(0, math.rad(90), 0))
		
		if Rotation == 1 then

			Rotation = 2
		elseif Rotation == 2 then

			Rotation = 3
		elseif Rotation == 3 then

			Rotation = 4
		elseif Rotation == 4 then

			Rotation = 1
		end
	end
end)

UserInputService.InputBegan:Connect(function(input, gameProcessedEvent)
	if input.KeyCode == Enum.KeyCode.X and not gameProcessedEvent then
		if IsSpawning == false or CurrentDecoy == nil then return end

		CurrentDecoy:Destroy()
		CurrentDecoy = nil
		IsSpawning = false
		SpawnButton.Visible = true
	end
end)

--//Set CurrentDecoy\\--

SendCurrentDecoy.OnClientEvent:Connect(function(TowerDecoy)
	
	CurrentDecoy = TowerDecoy
	Mouse.TargetFilter = CurrentDecoy
end)

Here is my server script:

local ServerStorage = game:GetService('ServerStorage')

local PlacementEvents = game:GetService('ReplicatedStorage'):WaitForChild('PlacementEvents')
local PlaceCheckPosition = PlacementEvents:WaitForChild('PlaceCheckPosition')
local CreateDecoyEvent = PlacementEvents:WaitForChild('CreateDecoyEvent')
local SendCurrentDecoy = PlacementEvents:WaitForChild('SendCurrentDecoy')

local ValidPart = workspace:WaitForChild('Maps'):WaitForChild('Tutorial'):WaitForChild('Placeable')

local connection

connection = PlaceCheckPosition.OnServerEvent:Connect(function(player, MousePos, Rotation, Tower)	
	if player.PlayerStats.Money.Value >= 100 then
		if Rotation == 1 or 2 or 3 or 4 then

			--player.PlayerStats.Money.Value -= 100

			local CheckPart = Instance.new('Part')
			CheckPart.Name = 'CheckPart'
			CheckPart.Anchored = true
			CheckPart.CanCollide = false
			CheckPart.Transparency = 0.5
			CheckPart.Size = Vector3.new(2,2,2)
			CheckPart.Parent = workspace
			CheckPart.Position = MousePos

			local PartsInValidZone = workspace:GetPartsInPart(ValidPart)

			for i, part in pairs(PartsInValidZone) do		
				if part.Name == 'CheckPart' then	

					local Towers = ServerStorage:WaitForChild('Towers')
					local TowerClone = Towers:FindFirstChild(Tower):Clone()
					workspace.Maps.Tutorial.DecoyTowers:ClearAllChildren()

					local posX = MousePos.X
					local posY = MousePos.Y + 0.8
					local posZ = MousePos.Z

					TowerClone.Parent = workspace.Maps.Tutorial.Towers
					TowerClone.Outline.Transparency = 1
					TowerClone:SetPrimaryPartCFrame(CFrame.new(Vector3.new(posX, posY, posZ))*CFrame.Angles(0,math.rad(90*Rotation),0))
					break
				end
			end
		end
	end
end)

CreateDecoyEvent.OnServerEvent:Connect(function(player, MousePos, Tower)
	
	local Towers = ServerStorage:WaitForChild('Towers')
	local TowerDecoy = Towers:FindFirstChild(Tower):Clone()

	TowerDecoy.Parent = workspace.Maps.Tutorial.DecoyTowers
	TowerDecoy:SetPrimaryPartCFrame(CFrame.new(MousePos))

	SendCurrentDecoy:FireClient(player, TowerDecoy)
end)

Do you see anything unnecessary or anything that is still vulnerable to exploiters? Thank you for the help btw.

You might wanna validate that the mouse position is within reasonable bounds same for the rotation

but besides that it looks good

That already gets checked in this for loop:

			for i, part in pairs(PartsInValidZone) do		
				if part.Name == 'CheckPart' then	

					local Towers = ServerStorage:WaitForChild('Towers')
					local TowerClone = Towers:FindFirstChild(Tower):Clone()
					workspace.Maps.Tutorial.DecoyTowers:ClearAllChildren()

					local posX = MousePos.X
					local posY = MousePos.Y + 0.8
					local posZ = MousePos.Z

					TowerClone.Parent = workspace.Maps.Tutorial.Towers
					TowerClone.Outline.Transparency = 1
					TowerClone:SetPrimaryPartCFrame(CFrame.new(Vector3.new(posX, posY, posZ))*CFrame.Angles(0,math.rad(90*Rotation),0))
					break
				end
			end

Thank you so much for the help!!!