Need help destroying builds with no support

I got a build system from a YouTube tutorial which I modified to use a tool, I need help on destroying builds that clearly have to support and preventing the play from building if there is no support.


my pants decided not to load in apparently either

--ServerScriptService

--|| SERVICES ||--
local ReplicatedStorage = game:GetService("ReplicatedStorage")
--|| FOLDERS ||--
local BuildingEventsFolder = ReplicatedStorage:WaitForChild("BuildingEvents")
--|| REMOTE EVENTS ||--
local PlaceBuildEvent = BuildingEventsFolder:WaitForChild("PlaceBuild")
--|| EVENTS ||--
PlaceBuildEvent.OnServerEvent:Connect(function(Player, BuildName, BuildPosition, BuildRotation)
	local BuildComponent = script:FindFirstChild(BuildName)
	if BuildComponent then
		BuildComponent = BuildComponent:Clone()
		BuildComponent.Parent = workspace:FindFirstChild("Builds") or workspace
		BuildComponent.Position = BuildPosition
		BuildComponent:WaitForChild("Builder").Value = Player
		BuildComponent.Orientation = BuildRotation
		BuildComponent.Anchored = true
		BuildComponent.CanCollide = true
	end
end)

BuildingEventsFolder.DeleteBuild.OnServerEvent:Connect(function(player, build)
	build:Destroy()
end)
-- MainBuildingScript located in starterpack

--|| SERVICES ||--
local UserInputService = game:GetService("UserInputService")
local ContextActionService = game:GetService("ContextActionService")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
--|| MODULES ||--
local BuildManagerComponentModule = require(script:WaitForChild("BuildManagerComponent"))
--|| VARIABLES ||--
local Player = Players.LocalPlayer
local Mouse = Player:GetMouse()
local CharacterModel = Player.Character or Player.CharacterAdded:Wait()
local HumanoidRootPart = CharacterModel:WaitForChild("HumanoidRootPart")
--|| FOLDERS ||--
local BuildMeshesFolder = script:WaitForChild("BuildMeshes")
local PreviewFolder = BuildMeshesFolder:WaitForChild("Preview")
local SFX = game.Workspace:WaitForChild("SFX")
--|| SETTINGS ||--
local BuildingMeshes = {
	["Wall"] = PreviewFolder:WaitForChild("Wall"),
	["Floor"] = PreviewFolder:WaitForChild("Floor"),
	["Ramp"] = PreviewFolder:WaitForChild("Ramp"),
}
local CFrameAddOns = {
	["Wall"] = CFrame.new(0,0,((BuildManagerComponentModule.GridSize/2)-.2)),
	["Ramp"] = CFrame.new(0,0,0),
	["Floor"] = CFrame.new(Vector3.new(0,(-BuildManagerComponentModule.GridSize/2)-.1,0))
}
--|| PRIVATE FUNCTIONS ||--
local function ResetPreviewParents(Mesh)
	for _,BuildMesh in pairs(BuildingMeshes) do
		if BuildMesh ~= Mesh and BuildMesh.Parent ~= PreviewFolder then
			BuildMesh.Parent = PreviewFolder
		end
	end
end
--|| ACTIONS ||--

--|| EVENTS ||--
UserInputService.InputBegan:Connect(function(InputObject, GameProcessed)
	if GameProcessed then return end
	if(InputObject.UserInputType == Enum.UserInputType.MouseButton1) then
		BuildManagerComponentModule.PlaceBuild(BuildingMeshes[BuildManagerComponentModule.SelectedBuild], BuildManagerComponentModule.SelectedBuild)
	end
end)
RunService.RenderStepped:Connect(function()
	if(BuildManagerComponentModule.isBuilding) then
		local BuildComponentPosition = BuildManagerComponentModule.GetNextBuildPosition(HumanoidRootPart.Position, Mouse.Hit.LookVector)
		local BuildComponentRotation = BuildManagerComponentModule.GetNextBuildRotation(Mouse.Hit.LookVector)
		
		local BuildComponent = BuildingMeshes[BuildManagerComponentModule.SelectedBuild]
		ResetPreviewParents(BuildComponent)
		BuildComponent.Parent = workspace:FindFirstChild("Builds") or workspace
		BuildComponent.CFrame = CFrame.new(BuildComponentPosition) * CFrame.Angles(BuildComponentRotation.X,BuildComponentRotation.Y,BuildComponentRotation.Z) * CFrameAddOns[BuildManagerComponentModule.SelectedBuild]
	else
		ResetPreviewParents()
	end
end)
-- BuildManagerComponent located in main building script

local BuildManagerComponent = {}
--|| MODULE SETTINGS ||--
BuildManagerComponent.GridSize = 10
BuildManagerComponent.BuildDistance = 5
--|| MODULE VARIABLES ||--
BuildManagerComponent.isBuilding = false
BuildManagerComponent.SelectedBuild = "Wall"
--|| TABLES & DICTIONARIES ||--
local BuildingKeybinds = {
}

local BuildPhrases = {
	
}
local player = game.Players.LocalPlayer

--|| SERVICES ||--
local ReplicatedStorage = game:GetService("ReplicatedStorage")
--|| FOLDERS ||--
local BuildingEventsFolder = ReplicatedStorage:WaitForChild("BuildingEvents")
local SFX = game.Workspace:WaitForChild("SFX")
--|| REMOTE EVENTS ||--
local PlaceBuildEvent = BuildingEventsFolder:WaitForChild("PlaceBuild")
--|| PRIVATE FUNCTIONS ||--
local function GridSnap(Value, Size)
	return (math.floor(Value/Size + 0.5) * Size)
end
local function GetTouchingParts(Part)
	local Connection = Part.Touched:Connect(function() end)
	local Results = Part:GetTouchingParts()
	Connection:Disconnect()
	return Results
end
--|| MODULE FUNCTIONS ||--
function BuildManagerComponent.GetNextBuildPosition(HumanoidRootPartPosition, MouseLookVector3)
	local DirectionVector3 = MouseLookVector3 * BuildManagerComponent.BuildDistance
	DirectionVector3 += HumanoidRootPartPosition
	return Vector3.new(
		GridSnap(DirectionVector3.X, BuildManagerComponent.GridSize),
		GridSnap(DirectionVector3.Y, BuildManagerComponent.GridSize) + BuildManagerComponent.GridSize/2,
		GridSnap(DirectionVector3.Z, BuildManagerComponent.GridSize)
	)
end
function BuildManagerComponent.GetNextBuildRotation(Vector)
	if(typeof(Vector) == "Vector3") then
		local Y = math.atan2(Vector.X, Vector.Z)
		return Vector3.new(0,GridSnap(Y, math.rad(-90)), 0)
	end
end
--|| TOGGLE BUILDING ||--
function BuildManagerComponent.ToggleBuildMode(ActionName, InputState, InputObject)
	if ActionName == "ToggleBuild" then
		if InputState == Enum.UserInputState.Begin then
			BuildManagerComponent.isBuilding = not BuildManagerComponent.isBuilding
			warn("Toggled Build Mode: ", BuildManagerComponent.isBuilding)
		end
	end
end

BuildingEventsFolder.ToggleBuilding.Event:Connect(function()
	BuildManagerComponent.isBuilding = not BuildManagerComponent.isBuilding
	warn("Toggled Build Mode: ", BuildManagerComponent.isBuilding)
end)


--|| SWITCH TO A DIFFERENT BUILD( Ramp, Wall, Floor, Etc) ||--
function BuildManagerComponent.SwitchBuild(ActionName, InputState, InputObject)
	if(ActionName == "SwitchBuild") then
		if(InputState == Enum.UserInputState.Begin) then
			BuildManagerComponent.isBuilding = true
			if(BuildManagerComponent.isBuilding and BuildingKeybinds[InputObject.KeyCode]) then
				BuildManagerComponent.SelectedBuild = BuildingKeybinds[InputObject.KeyCode]
			end
		end
	end
end

BuildingEventsFolder.SwitchBuild.Event:Connect(function(Build)
	if(BuildManagerComponent.isBuilding) then
		BuildManagerComponent.SelectedBuild = Build
	end
end)

function BuildManagerComponent.PlaceBuild(BuildMesh, BuildName)
	if(BuildManagerComponent.isBuilding) then
		local Results = GetTouchingParts(BuildMesh)
		local Placeable = true
		for _, Build in pairs(Results) do
			if Build.Name == BuildMesh.Name then
				if Build.Position == BuildMesh.Position then
					Placeable = false
				end
			end
		end
		if(Placeable) then
			PlaceBuildEvent:FireServer(BuildName, BuildMesh.Position, BuildMesh.Orientation)
			SFX.Build:Play()
		end
	end
end
return BuildManagerComponent
4 Likes

i would probably check if it is touching anything and start from there

1 Like

if I were to do that, anything else floating would be touching it therefore it wouldnā€™t work at all

edit - I thought I had an idea but it didnā€™t work

1 Like

Sounds pretty fun. Heres how youd do it:

  • Youd basically wanna recursively check the parts that are touching the main part.

  • Youd be checking to see if any of those parts are touching the ground.

  • Youd also need to make sure you donā€™t include parts that have already been identified in your recursive check, to prevent looping and making ur PC go bye bye

Its not as hard as you may think it is.

(thought of this in like a minute so im sure thereā€™s a few holes, but you would patch them as you progressed)

2 Likes

would a loop like this work out fine?

while true do
	wait(1)
	a = script.Parent
	for i,v in pairs(a:GetTouchingParts()) do
		print(v)
	end
end

also Iā€™m wondering how I can occlude already listed parts?

1 Like

i reccomend you watch videos on how recursion works, thatā€™s not exactly how you do it. Once you watch those, what I said will make more sense to you.

2 Likes

i mean to have anything else floating in the first place, it would have to be destroyed, pretty foolproof if you ask me.

1 Like

well yea but that floating thing can also be touching another floating thing. Thats why you need to recursively check if thereā€™s a chain of parts going down to the ground. If not, then destroy the whole thing.

1 Like

i see your point, but in the terms of only creating, this would be fine, as the first thing placed would always have to touch something, so does the next. But if the parts can be destroyed, this could be a bit trickier

1 Like

for destroying, you could just do what youd normally do, just add the part being destroyed into the exclusion list from the get go.

1 Like

so what I needs is a recursion that checks all parts touching it, and exclude all the parts that arenā€™t the base part and destroy the part if the base part is broken?

1 Like

kinda, i dunno if i explained it right so ill just write the code and send the place. give me like 30 minutes.

1 Like

yeah Iā€™m still pretty confused, but thank you and take all the time you need.

1 Like

i finished, ill go ahead and start writing out the answer and explaining stuff. itl take me a while.

1 Like

So basically, You wanna use recursion and go through chains of touching parts. Starting with the main part.

I will be providing a clip, an example place, and some code evaluation to explain how that place and clip works.

a demo: Baseplate - Roblox Studio (gyazo.com)

As @foxnoobkite Foxnoobkite mentioned, if you were to be deleting something, things could be trickier, which is very true. So I fixed that : Roblox Studio (gyazo.com)

The code searches all of the pathways until it finds one touching the ground. Your method of identifying the ground can be totally up to you. In my case, I just named the baseplate ā€œGroundā€. But you can do whatever. Once it finds a pathway touching the ground, it automatically stops and returns, bypassing any more searching. this is so your PC doesnā€™t have to do any extra searching for no reason.

hereā€™s the code broken down into pieces (its less than 100 lines!!!)

firstly, this is located in a local script because I felt like it. You would certainly be applying this concept on the server side.

repeat
	task.wait() -- we need the game to load first.
until game:IsLoaded()
--gonna refer our folders
local OtherPartsFolder = workspace:WaitForChild("OtherParts") --just other parts
local MainPartsFolder = workspace:WaitForChild("MainParts") --parts we wanna check
local function RecursivelyCheckPart(Part:BasePart,ExclusionList:{BasePart?}) :boolean
	
	local TouchingParts = Part:GetTouchingParts()
	
	for i,Part in TouchingParts do --we need to remove any blacklisted parts from the search
		if table.find(ExclusionList,Part) then
			table.remove(TouchingParts,i) 
		end
	end
	
	if TouchingParts == {} or TouchingParts == nil then -- i dunno if it returns a blank array or nil if its touching nothing
		return false --we return false because its not touching anything
	end
	
	for i,Part in TouchingParts do 
		if Part.Name == "Ground" then --your method of identifying what the ground is, is totally up to you.
			return true
			
		else 
			
			table.insert(ExclusionList,Part) --add this part to the exclusion list
			
			local Touching = RecursivelyCheckPart(Part,ExclusionList)
			
			if Touching  then
				return Touching
			end
			
		end
		
	end
	
	return false
end
local function Check() --looping through the main parts folder and then doing recursion touch checks
	local MainParts:{BasePart?} = MainPartsFolder:GetChildren()
	
	for index,MainPart:BasePart in MainParts do
		
		local Supported = false
		
		
		Supported = RecursivelyCheckPart(MainPart,{MainPart})
		
		if Supported then
			MainPart.Color = Color3.fromRGB(77, 255, 0) --change the color of the part to green.
		else
			MainPart.Color = Color3.fromRGB(255, 0, 4) --change the color of the part to red.
		end
		
	end
	
end
--Looping the main code--

while true do 
	task.wait(0.25) -- DO NOT RUN THIS FUNCTION EVERY FRAME šŸ˜­šŸ˜­
	Check() 
end

Recursion can be pretty tricky to wrap ur head around, so you might need to watch more videos or articles.

But im sure you want to try it out for yourself, so here:
recursiveGroundFinder.rbxl (55.1 KB)

just use the size tool and mess around with the parts.

Hope this helps.

1 Like

ill try this out, if it works ill mark it as a solution tysm!

1 Like

so I do have one more question, how can I move the part into BasePartsFolder when I place it on the ground?

edit- let me rephrase this, so when I build a wall or ramp, how could i move it to the baseparts folder if its touching the ground when i place it
edit 2 - so looking at the script i can see it does this for me, but when i place new walls they arent added to the exclusion list, ill see what i can do to solve this issue

1 Like

The thing i sent above was an example of how youd apply this method. In your case youd just need to update it each time you place an object or something. Ideally youd wanna take ths resources i gave you and use those to create a perfectly fitting solution.

All ive really done is gave you the concept and code to back it

So ive figured out how it works, only problem im having is putting it in a server script :sweat_smile: also i could theoretically do this for all of my structures?

2 Likes

Yup, its just recursive destruction. So you can just destroy a part

1 Like