AutoQuest AutoFarm AutoBuild
Keyless Verified

AutoQuest AutoFarm AutoBuild

Build A Boat For Treasure32tq32tq 215.3K views 731 executesUpdated Apr 3, 2022
5.0/ 5(8 votes)
80100% Positive
Lua Script•2827 lines (85532 chars)
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local StarterGui = game:GetService("StarterGui")
local UserInputService = game:GetService("UserInputService")
local HttpService = game:GetService("HttpService")
local Workspace = game:GetService("Workspace")
local player = Players.LocalPlayer
local UI_URL = "https://raw.githubusercontent.com/madonchik123/NEW_UI_LIB/refs/heads/executor/UI_LIB.lua"
if game.PlaceId ~= 537413528 then
	warn("Open Build a Boat for Treasure first")
	return
end
if type(loadstring) ~= "function" then
	warn("This executor does not support loadstring")
	return
end
local playerGui = player:WaitForChild("PlayerGui", 10)
if not playerGui then
	warn("PlayerGui has not loaded")
	return
end
local cfg = {
	Farm = false,
	TravelTime = 30,
	Height = 65,
	SpawnDelay = 3,
	ChestWait = 35,
	Dashboard = false,
	SoccerMode = "Goal then chest",
}
local state = {
	Alive = true,
	Status = "Idle",
	Job = nil,
	Motion = nil,
	Connections = {},
	ValueConnections = {},
	ChestCharacters = setmetatable({}, { __mode = "k" }),
	ClaimedFrames = setmetatable({}, { __mode = "k" }),
	Started = os.clock(),
	Arrivals = 0,
	Deaths = 0,
	GoldEarned = 0,
	BlocksEarned = 0,
	QuestCooldown = 0,
	GlueRequests = 0,
}
local ui = {}
local library, window
local setDashboard, updateDashboard, buildImporterUi, openBrowser, clearPreview, removeSoccerGlue
local syncingToggle = false
local function findPath(root, ...)
	for _, name in ipairs({ ... }) do
		root = root and root:FindFirstChild(name)
	end
	return root
end
local function zone()
	return Workspace:FindFirstChild(tostring(player.TeamColor) .. "Zone")
end
local function activeQuest()
	local value = findPath(zone(), "QuestNum")
	return if value then value.Value else 0
end
local function questCompleted(id)
	local key = "Q" .. tostring(id)
	if id == 101 then
		key ..= tostring(os.date("!*t").year)
	end
	return findPath(player, "OtherData", key) ~= nil
end
local function questCard(id)
	return findPath(
		player,
		"PlayerGui",
		"ShopGui",
		"MainFrame",
		"TabFrame",
		"QuestFrame",
		"ScrollingFrame",
		"Quest" .. id
	) or findPath(StarterGui, "ShopGui", "MainFrame", "TabFrame", "QuestFrame", "ScrollingFrame", "Quest" .. id)
end
local function questAvailable(id)
	local card = questCard(id)
	return card ~= nil and card.Visible
end
local function numberValue(folder, name)
	local value = findPath(player, folder, name)
	return if value and type(value.Value) == "number" then value.Value else nil
end
local function characterParts()
	local character = player.Character
	local root = character and character:FindFirstChild("HumanoidRootPart")
	local humanoid = character and character:FindFirstChildOfClass("Humanoid")
	if character and character.Parent and root and humanoid and humanoid.Health > 0 then
		return character, root, humanoid
	end
	return nil
end
local function current(job)
	return state.Alive and state.Job == job
end
local function sameCharacter(job)
	return current(job)
		and player.Character == job.Character
		and job.Character.Parent ~= nil
		and job.Root.Parent == job.Character
		and job.Humanoid.Health > 0
end
local function pause(job, seconds, requireCharacter)
	local deadline = os.clock() + seconds
	repeat
		if not current(job) or (requireCharacter and not sameCharacter(job)) then
			return false
		end
		task.wait(math.min(0.1, math.max(0, deadline - os.clock())))
	until os.clock() >= deadline
	return current(job) and (not requireCharacter or sameCharacter(job))
end
local function requestsPending()
	return state.BuildBusy == true or state.GlueRequests > 0 or state.PendingGlue ~= nil
end
local function syncToggles()
	syncingToggle = true
	if ui.Farm then
		ui.Farm:Set(cfg.Farm, true)
	end
	if ui.Quests then
		ui.Quests:Set(state.Job ~= nil and state.Job.Kind == "Quest", true)
	end
	if ui.Build then
		ui.Build:SetDisabled(state.Job ~= nil or requestsPending())
	end
	if ui.StopBuild then
		ui.StopBuild:SetDisabled(not state.BuildBusy and not (state.Job and state.Job.Kind == "Build"))
	end
	if ui.Preview then
		ui.Preview:SetDisabled(state.Job ~= nil or requestsPending())
	end
	syncingToggle = false
end
local function releaseMotion(job)
	local motion = state.Motion
	if job and motion and motion.Job ~= job then
		return
	end
	state.Motion = nil
	if not motion then
		return
	end
	if motion.Glue then
		removeSoccerGlue(motion)
	end
	for _, connection in ipairs(motion.Connections) do
		connection:Disconnect()
	end
	for _, object in ipairs(motion.Objects) do
		object:Destroy()
	end
	if motion.Ball and motion.Ball.Parent then
		motion.Ball.CanCollide = motion.BallCollision
	end
	for part, collision in pairs(motion.Collisions) do
		if part.Parent then
			part.CanCollide = collision
		end
	end
	if motion.Humanoid.Parent then
		motion.Humanoid.AutoRotate = motion.AutoRotate
		motion.Humanoid.PlatformStand = motion.PlatformStand
	end
	if motion.Root.Parent then
		motion.Root.AssemblyLinearVelocity = Vector3.zero
		motion.Root.AssemblyAngularVelocity = Vector3.zero
	end
end
local function stopJob(message)
	local job = state.Job
	state.Job = nil
	cfg.Farm = false
	if job and job.Kind == "Farm" and job.Thread and job.Thread ~= coroutine.running() then
		pcall(task.cancel, job.Thread)
	end
	releaseMotion(job)
	syncToggles()
	if message then
		state.Status = message
	end
end
local function acquireMotion(job)
	if not sameCharacter(job) then
		error("Character changed before movement", 0)
	end
	releaseMotion(job)
	if job.Root.Anchored then
		error("Wait for the game to release your anchored character", 0)
	end
	local motion = {
		Job = job,
		Root = job.Root,
		Humanoid = job.Humanoid,
		AutoRotate = job.Humanoid.AutoRotate,
		PlatformStand = job.Humanoid.PlatformStand,
		Collisions = {},
		Connections = {},
		Objects = {},
	}
	state.Motion = motion
	local function trackPart(part)
		if part:IsA("BasePart") and motion.Collisions[part] == nil then
			motion.Collisions[part] = part.CanCollide
			part.CanCollide = false
		end
	end
	for _, part in ipairs(job.Character:GetDescendants()) do
		trackPart(part)
	end
	table.insert(motion.Connections, job.Character.DescendantAdded:Connect(trackPart))
	local attachment = Instance.new("Attachment")
	table.insert(motion.Objects, attachment)
	attachment.Name = "BABFTMovement"
	attachment.Parent = job.Root
	local velocity = Instance.new("LinearVelocity")
	table.insert(motion.Objects, velocity)
	velocity.Attachment0 = attachment
	velocity.RelativeTo = Enum.ActuatorRelativeTo.World
	velocity.VelocityConstraintMode = Enum.VelocityConstraintMode.Vector
	velocity.ForceLimitsEnabled = false
	velocity.VectorVelocity = Vector3.zero
	velocity.Parent = job.Root
	motion.Velocity = velocity
	job.Humanoid.Sit = false
	job.Humanoid.AutoRotate = false
	job.Humanoid.PlatformStand = true
	job.Root.AssemblyLinearVelocity = Vector3.zero
	table.insert(
		motion.Connections,
		RunService.Stepped:Connect(function()
			if state.Motion ~= motion then
				return
			end
			if not sameCharacter(job) then
				releaseMotion(job)
				return
			end
			for part in pairs(motion.Collisions) do
				if part.Parent then
					part.CanCollide = false
				else
					motion.Collisions[part] = nil
				end
			end
			job.Root.AssemblyAngularVelocity = Vector3.zero
		end)
	)
end
local function moveTo(job, target, duration)
	if not sameCharacter(job) or not state.Motion then
		return false
	end
	local motion = state.Motion
	if motion.Ball then
		duration = math.max(duration, (job.Root.Position - target.Position).Magnitude / 100)
	end
	local speed = math.clamp((job.Root.Position - target.Position).Magnitude / math.max(0.1, duration), 4, 800)
	local deadline = os.clock() + duration + 4
	while sameCharacter(job) and state.Motion == motion and os.clock() < deadline do
		if job.Kind == "Quest" and job.QuestId and (zone() ~= job.QuestZone or activeQuest() ~= job.QuestId) then
			break
		end
		if motion.Ball then
			if not motion.Ball.Parent or motion.Ball.Anchored or not motion.Glue or not motion.Glue.Parent then
				break
			end
			local destination = job.Root.CFrame:PointToWorldSpace(motion.BallLocal)
			if (motion.Ball.Position - destination).Magnitude > 3 then
				break
			end
		end
		local delta = target.Position - job.Root.Position
		if delta.Magnitude < 0.5 then
			motion.Velocity.VectorVelocity = Vector3.zero
			job.Root.AssemblyLinearVelocity = Vector3.zero
			return true
		end
		motion.Velocity.VectorVelocity = delta.Unit * math.min(speed, delta.Magnitude * 12)
		RunService.Heartbeat:Wait()
	end
	if state.Motion == motion then
		motion.Velocity.VectorVelocity = Vector3.zero
	end
	return false
end
local function waitForCharacter(job, previousCharacter, timeout)
	local deadline = os.clock() + timeout
	while current(job) and os.clock() < deadline do
		local character, root, humanoid = characterParts()
		if character and character ~= previousCharacter and not root.Anchored then
			job.Character, job.Root, job.Humanoid = character, root, humanoid
			if pause(job, cfg.SpawnDelay, true) then
				return true
			end
		end
		task.wait(0.1)
	end
	return false
end
local function getRoute()
	local stages = findPath(Workspace, "BoatStages", "NormalStages")
	local trigger = findPath(stages, "TheEnd", "GoldenChest", "Trigger")
	if not (trigger and trigger:IsA("BasePart")) then
		error("Golden chest is not loaded", 0)
	end
	local gates = {}
	for _, model in ipairs(stages:GetChildren()) do
		if model.Name:match("^CaveStage%d+$") then
			local gate = model:FindFirstChild("DarknessPart")
			if gate and gate:IsA("BasePart") then
				table.insert(gates, gate)
			end
		end
	end
	if #gates ~= 10 then
		error("River route is incomplete; expected 10 loaded cave gates", 0)
	end
	table.sort(gates, function(a, b)
		return a.Position.Z < b.Position.Z
	end)
	return gates, trigger
end
local function claimResults()
	local gui = findPath(player, "PlayerGui", "RiverResultsGui")
	local frame = gui and gui:FindFirstChild("Frame")
	if not gui or not gui.Enabled or not frame or not frame.Visible or state.ClaimedFrames[frame] then
		return
	end
	local remote = Workspace:FindFirstChild("ClaimRiverResultsGold")
	if not remote or not remote:IsA("RemoteEvent") then
		return
	end
	state.ClaimedFrames[frame] = true
	remote:FireServer()
	frame.Visible = false
	if not cfg.Dashboard then
		pcall(StarterGui.SetCoreGuiEnabled, StarterGui, Enum.CoreGuiType.Backpack, true)
	end
end
local function chestAcknowledged(job)
	local goldBlocks = numberValue("Data", "GoldBlock")
	local endValue = findPath(player, "OtherData", "End")
	return (goldBlocks and job.GoldBlocks and goldBlocks > job.GoldBlocks)
		or (endValue and endValue.Value ~= "" and endValue.Value ~= job.EndValue)
		or job.Character:FindFirstChild("TreasureEndEffectsLS", true) ~= nil
end
local function runTrip(job)
	local gates, trigger = getRoute()
	local first, last = gates[1].Position, gates[#gates].Position
	local start = Vector3.new(first.X, cfg.Height, first.Z - 1540)
	local finish = Vector3.new(last.X, cfg.Height, last.Z + 450)
	state.Status = "Moving to river entrance"
	acquireMotion(job)
	if not moveTo(job, CFrame.new(start), 1.5) then
		return false
	end
	local distance, previousZ = finish.Z - start.Z, start.Z
	for index, gate in ipairs(gates) do
		state.Status = string.format("River gate %d / %d", index, #gates)
		local target = Vector3.new(gate.Position.X, cfg.Height, gate.Position.Z)
		local duration = cfg.TravelTime * (target.Z - previousZ) / distance
		if not moveTo(job, CFrame.new(target), math.max(0.2, duration)) then
			return false
		end
		previousZ = target.Z
	end
	if not moveTo(job, CFrame.new(finish), cfg.TravelTime * (finish.Z - previousZ) / distance) then
		return false
	end
	if not (trigger.Parent and not state.ChestCharacters[job.Character]) then
		error("Respawn before another chest activation", 0)
	end
	state.Status = "Approaching chest"
	local approach = trigger.CFrame * CFrame.new(0, 0, trigger.Size.Z / 2 + 6)
	if not moveTo(job, approach, 2.5) or not sameCharacter(job) then
		return false
	end
	job.GoldBlocks = numberValue("Data", "GoldBlock")
	local endValue = findPath(player, "OtherData", "End")
	job.EndValue = endValue and endValue.Value
	state.ChestCharacters[job.Character] = true
	state.Status = "Chest activated; waiting for server"
	if type(firetouchinterest) == "function" then
		local ok, problem = pcall(firetouchinterest, job.Root, trigger, 0)
		pcall(firetouchinterest, job.Root, trigger, 1)
		if not ok then
			error(tostring(problem), 0)
		end
	else
		if not moveTo(job, trigger.CFrame, 0.6) then
			if chestAcknowledged(job) then
				state.Arrivals += 1
				return true
			end
			return false
		end
	end
	state.Arrivals += 1
	local deadline = os.clock() + cfg.ChestWait
	while sameCharacter(job) and os.clock() < deadline do
		if chestAcknowledged(job) then
			state.Status = "Chest acknowledged; waiting for respawn"
			releaseMotion(job)
		end
		task.wait(0.1)
	end
	releaseMotion(job)
	if sameCharacter(job) then
		state.Status = "Chest wait expired; resetting this character"
		job.Humanoid.Health = 0
	end
	return true
end
local function farm(job)
	local previousCharacter
	while current(job) and cfg.Farm do
		state.Status = "Waiting for a fresh character"
		if not (waitForCharacter(job, previousCharacter, 45)) then
			error("No fresh character within 45 seconds; farm paused", 0)
		end
		if state.ChestCharacters[job.Character] or job.Root.Position.Z > 900 then
			error("Respawn at your plot before restarting the farm", 0)
		end
		if activeQuest() ~= 0 then
			error("Finish or cancel your active quest before farming", 0)
		end
		local reached = runTrip(job)
		releaseMotion(job)
		if not current(job) then
			return
		end
		previousCharacter = job.Character
		if not reached then
			if sameCharacter(job) then
				error("Movement interrupted", 0)
			end
			state.Deaths += 1
		end
	end
end
local function teamLeader()
	local leader = findPath(player.Team, "TeamLeader")
	return leader and leader.Value == player.Name
end
local function requestQuest(id)
	if not teamLeader() then
		error("Only your team leader can start or cancel quests", 0)
	end
	if os.clock() < state.QuestCooldown then
		error("Wait before another quest request", 0)
	end
	if id ~= 0 then
		if activeQuest() ~= 0 then
			error("Finish or cancel the active quest first", 0)
		end
		if not questAvailable(id) then
			error("This quest is unavailable or its menu has not loaded", 0)
		end
		local blocks = Workspace:FindFirstChild("Blocks")
		if not blocks then
			error("Building data has not loaded", 0)
		end
		for _, teammate in ipairs(player.Team:GetPlayers()) do
			local folder = blocks:FindFirstChild(teammate.Name)
			if folder then
				for _, block in ipairs(folder:GetChildren()) do
					if block:IsA("Model") and block.PrimaryPart and block.PrimaryPart.Position.Z < 900 then
						error("Clear your team's build before starting a quest; builds are never cleared automatically")
					end
				end
			end
		end
	end
	local remote = Workspace:FindFirstChild("QuestMakerEvent")
	if not (remote and remote:IsA("RemoteEvent")) then
		error("QuestMakerEvent is unavailable", 0)
	end
	state.QuestCooldown = os.clock() + 1.5
	remote:FireServer(id)
	state.Status = "Starting quest"
end
local function awaitQuest(job, id)
	local deadline = os.clock() + 10
	while current(job) and activeQuest() ~= id and os.clock() < deadline do
		task.wait(0.1)
	end
	if not (current(job) and activeQuest() == id) then
		error("Quest state did not update", 0)
	end
end
local function questObjectName(object, root)
	local names = {}
	while object and object ~= root do
		table.insert(names, object.Name:lower())
		object = object.Parent
	end
	return table.concat(names, "/")
end
local function questTargetScore(name, id, height)
	for _, word in ipairs({ "kill", "lava", "water", "death", "reset", "fail", "boundary", "ground", "floor" }) do
		if name:find(word, 1, true) then
			return nil
		end
	end
	local hint = if id == 1 then "cloud" elseif id == 2 then "target" else "find"
	return (if name:find(hint, 1, true) then 100 else 0) + math.clamp(height, 0, 60)
end
local function questInteractions(id, root)
	local candidates, seen = {}, {}
	local plot = zone()
	for _, object in ipairs(root:GetDescendants()) do
		local part
		if id == 4 and object:IsA("ClickDetector") and object.MaxActivationDistance > 0 then
			local parent = object.Parent
			part = if parent:IsA("BasePart") then parent elseif parent:IsA("Model") then parent.PrimaryPart else nil
		elseif id ~= 4 and object:IsA("TouchTransmitter") and object.Parent:IsA("BasePart") then
			part = object.Parent
		end
		if part and (id == 4 or part.CanTouch) and not seen[part] then
			seen[part] = true
			local name = questObjectName(part, root)
			local score = questTargetScore(name, id, part.Position.Y - (if plot then plot.Position.Y else 0))
			if score then
				table.insert(candidates, { Part = part, Interaction = object, Score = score, Name = name })
			end
		end
	end
	table.sort(candidates, function(a, b)
		if a.Score == b.Score then
			return a.Name < b.Name
		end
		return a.Score > b.Score
	end)
	return candidates
end
local function pulseTouch(first, second)
	if type(firetouchinterest) ~= "function" then
		return
	end
	local ok, problem = pcall(firetouchinterest, first, second, 0)
	pcall(firetouchinterest, first, second, 1)
	if not ok then
		warn("[BABFT] Touch: " .. tostring(problem))
	end
end
local function waitQuestResult(job, id, seconds)
	local deadline = os.clock() + seconds
	repeat
		if not current(job) or zone() ~= job.QuestZone then
			return false
		end
		if questCompleted(id) then
			return true
		end
		if activeQuest() ~= 0 and activeQuest() ~= id then
			return false
		end
		task.wait(0.1)
	until os.clock() >= deadline
	return current(job) and zone() == job.QuestZone and questCompleted(id)
end
local function assistQuest(job, id)
	if not waitForCharacter(job, nil, 15) then
		error("No living character available", 0)
	end
	local origin = job.Root.CFrame
	local deadline, emptySince = os.clock() + 45, os.clock()
	local tried, lastNames = {}, {}
	while sameCharacter(job) and zone() == job.QuestZone and os.clock() < deadline do
		if questCompleted(id) then
			break
		end
		if activeQuest() ~= id then
			break
		end
		local root = findPath(job.QuestZone, "Quest")
		local candidates = if root then questInteractions(id, root) else {}
		local selected, leastAttempts
		for index, candidate in ipairs(candidates) do
			if index > 16 then
				break
			end
			local previous = tried[candidate.Part]
			if previous and (candidate.Part.Position - previous.Position).Magnitude > 2 then
				previous = nil
			end
			local count = if previous then previous.Count else 0
			if count < 2 and (leastAttempts == nil or count < leastAttempts) then
				selected = candidate
				leastAttempts = count
			end
		end
		if not selected then
			releaseMotion(job)
			if #candidates > 0 or os.clock() - emptySince > 8 then
				break
			end
			state.Status = "Waiting for quest objects"
			if not pause(job, 0.25, true) then
				break
			end
			continue
		end
		emptySince = os.clock()
		local part, interaction = selected.Part, selected.Interaction
		table.insert(lastNames, selected.Name)
		if not state.Motion then
			acquireMotion(job)
		end
		state.Status = (if id == 1 then "Cloud" elseif id == 2 then "Target" else "Find Me") .. ": " .. part.Name
		local position = part.Position
		local previous = tried[part]
		local attempts = if previous and (position - previous.Position).Magnitude < 2 then previous.Count else 0
		tried[part] = { Position = position, Count = attempts + 1 }
		local approach = part.CFrame
			* (
				if id == 4
					then CFrame.new(0, 0, math.min(3, interaction.MaxActivationDistance / 2))
					else CFrame.new(0, part.Size.Y / 2 + 4, 0)
			)
		local duration = math.clamp((job.Root.Position - approach.Position).Magnitude / 180, 0.4, 5)
		local reached = moveTo(job, approach, duration)
		if reached and part.Parent and sameCharacter(job) and activeQuest() == id then
			if id == 4 then
				fireclickdetector(interaction, (job.Root.Position - part.Position).Magnitude, "MouseClick")
			else
				reached = moveTo(job, part.CFrame, 0.5)
				if reached and sameCharacter(job) and part.Parent and activeQuest() == id then
					pulseTouch(job.Root, part)
				end
			end
		end
		if waitQuestResult(job, id, 1.25) then
			break
		end
	end
	job.QuestId = nil
	if sameCharacter(job) and zone() == job.QuestZone and state.Motion then
		moveTo(job, origin, math.clamp((job.Root.Position - origin.Position).Magnitude / 200, 0.5, 5))
	end
	releaseMotion(job)
	if not waitQuestResult(job, id, 1.5) then
		error("Quest " .. id .. " was not confirmed; tried " .. table.concat(lastNames, ", "), 0)
	end
	state.Status = "Quest completed"
end
local function soccerBall(root)
	local best, bestScore, tied
	for _, part in ipairs(root:GetDescendants()) do
		if not part:IsA("BasePart") or part.Anchored then
			continue
		end
		local assembly = part.AssemblyRootPart
		if assembly and assembly.Anchored then
			continue
		end
		local name = questObjectName(part, root)
		local kick = part:FindFirstChild("KickRE")
		local round = part:IsA("Part") and part.Shape == Enum.PartType.Ball
		local mesh = part:FindFirstChildOfClass("SpecialMesh")
		round = round or (mesh and mesh.MeshType == Enum.MeshType.Sphere)
		local score = (if kick and kick:IsA("RemoteEvent") then 100 else 0)
			+ (if name:find("ball", 1, true) then 50 else 0)
			+ (if round then 25 else 0)
		if score == 0 then
			continue
		end
		if not bestScore or score > bestScore then
			best, bestScore, tied = part, score, false
		elseif score == bestScore and part.AssemblyRootPart ~= best.AssemblyRootPart then
			tied = true
		end
	end
	return if not tied then best else nil
end
local function soccerGoal(root, ball)
	local best, bestScore
	for _, object in ipairs(root:GetDescendants()) do
		if not object:IsA("BasePart") or object == ball or object.AssemblyRootPart == ball.AssemblyRootPart then
			continue
		end
		local name = questObjectName(object, root)
		if not (name:find("goal", 1, true) or name:find("net", 1, true)) then
			continue
		end
		local trigger = object:FindFirstChildOfClass("TouchTransmitter") ~= nil
		if not trigger then
			continue
		end
		local score = (if object.Name:lower():find("goal", 1, true) then 50 else 0)
			+ (if object.Transparency > 0.8 then 20 else 0)
			+ math.min(object.Size.Magnitude, 30)
		if not bestScore or score > bestScore then
			best, bestScore = object, score
		end
	end
	if best then
		return { Frame = best.CFrame, Part = best }
	end
	for _, object in ipairs(root:GetDescendants()) do
		local name = object.Name:lower()
		if object:IsA("Model") and (name == "goal" or name == "net" or name == "soccergoal") then
			local frame = object:GetBoundingBox()
			return { Frame = frame }
		end
	end
end
local function soccerTool(name)
	local tool = findPath(player, "Backpack", name) or findPath(player.Character, name)
	local remote = findPath(tool, "RF")
	if not remote or not remote:IsA("RemoteFunction") then
		error("Soccer needs " .. name, 0)
	end
	return remote
end
removeSoccerGlue = function(motion)
	local glue = motion.Glue
	if motion.GlueDeleting or not glue then
		return
	end
	if not glue.Parent then
		motion.Glue = nil
		if state.PendingGlue == motion then
			state.PendingGlue = nil
		end
		return
	end
	motion.GlueDeleting = true
	state.PendingGlue = motion
	state.GlueRequests += 1
	task.spawn(function()
		local problem
		for _ = 1, 2 do
			if glue.Parent ~= motion.GlueFolder then
				break
			end
			local ok, failure = pcall(function()
				motion.GlueDelete:InvokeServer(glue)
			end)
			if not ok then
				problem = failure
			end
			local deadline = os.clock() + 5
			while glue.Parent == motion.GlueFolder and os.clock() < deadline do
				task.wait(0.1)
			end
		end
		motion.GlueDeleting = false
		state.GlueRequests -= 1
		if glue.Parent ~= motion.GlueFolder then
			motion.Glue = nil
			if state.PendingGlue == motion then
				state.PendingGlue = nil
			end
		else
			warn(
				"[BABFT] Delete the temporary Soccer Glue with DeleteTool before continuing: "
					.. tostring(problem or "removal unconfirmed")
			)
			if state.Alive then
				state.Status = "Delete temporary Soccer Glue to continue"
			end
		end
		if state.Alive then
			syncToggles()
		end
	end)
end
local function soccerGluePoint(plot, ball, size)
	local center = plot.CFrame:PointToObjectSpace(ball.Position)
	local margin = size.Magnitude / 2 + 0.1
	local maxX, maxZ = plot.Size.X / 2 - margin, plot.Size.Z / 2 - margin
	local minX = 62.6 + margin
	if maxX < minX or maxZ <= 0 then
		return nil
	end
	local position = plot.CFrame:PointToWorldSpace(
		Vector3.new(math.clamp(center.X, minX, maxX), center.Y, math.clamp(center.Z, -maxZ, maxZ))
	)
	local relative = ball.CFrame:PointToObjectSpace(position)
	local radii = ball.Size / 2
	if (relative / radii).Magnitude > 0.95 then
		return nil
	end
	return position
end
local function soccerConnected(glue, root, ball)
	local part = glue and glue.Parent and glue.PrimaryPart
	if not part then
		return false
	end
	local characterConnected, ballConnected = false, false
	for _, connected in ipairs(part:GetConnectedParts(true)) do
		if connected == root then
			characterConnected = true
		end
		if connected == ball then
			ballConnected = true
		end
	end
	return characterConnected and ballConnected
end
local function attachSoccerBall(job, ball)
	if not sameCharacter(job) or not ball.Parent or ball.Anchored or activeQuest() ~= 8 then
		return false
	end
	if job.GlueAttempted then
		error("Soccer Glue attachment already attempted; restart the quest to retry", 0)
	end
	local plot = job.QuestZone
	local folder = findPath(Workspace, "Blocks", player.Name)
	local data = findPath(player, "Data", "Glue")
	local used = findPath(data, "Used")
	local template = findPath(game:GetService("ReplicatedStorage"), "BuildingParts", "Glue")
	if not folder or not template or not template.PrimaryPart then
		error("Glue building data is unavailable", 0)
	end
	if not data or data.Value - (if used then used.Value else 0) < 1 then
		error("Soccer needs one available Glue block", 0)
	end
	if ball:HasTag("NoNewWelds") or job.Root:HasTag("NoNewWelds") then
		error("Soccer attachment is blocked by NoNewWelds", 0)
	end
	local build, delete = soccerTool("BuildingTool"), soccerTool("DeleteTool")
	local point = soccerGluePoint(plot, ball, template.PrimaryPart.Size)
	if not point then
		error("Move the ball into the Soccer building area before attaching Glue", 0)
	end
	if not state.Motion then
		acquireMotion(job)
	end
	state.Status = "Soccer: placing Glue inside character and ball"
	if not moveTo(job, CFrame.new(point), 2) or not pause(job, 0.5, true) then
		return false
	end
	point = soccerGluePoint(plot, ball, template.PrimaryPart.Size)
	if not point or (job.Root.Position - point).Magnitude > 1 then
		error("Ball moved before Glue placement", 0)
	end
	local motion = state.Motion
	local frame = CFrame.new(point)
	local before = {}
	for _, model in ipairs(folder:GetChildren()) do
		before[model] = true
	end
	motion.GlueDelete, motion.GlueFolder = delete, folder
	job.GlueAttempted = true
	state.GlueRequests += 1
	local ok, problem = pcall(function()
		local sent, response = pcall(function()
			return build:InvokeServer(
				"Glue",
				data.Value,
				ball,
				ball.CFrame:ToObjectSpace(frame),
				false,
				frame,
				false,
				false
			)
		end)
		local deadline = os.clock() + 12
		repeat
			local match
			for _, model in ipairs(folder:GetChildren()) do
				if not before[model] and model:IsA("Model") and model.Name == "Glue" and model.PrimaryPart then
					if model == response or (model.PrimaryPart.Position - point).Magnitude <= 3 then
						if match then
							error("Multiple new Glue blocks matched the request", 0)
						end
						match = model
					end
				end
			end
			if match then
				motion.Glue = match
				return
			end
			task.wait(0.1)
		until os.clock() >= deadline
		if not sent then
			error(tostring(response), 0)
		end
		error("Server did not place Soccer Glue; no second block was requested", 0)
	end)
	state.GlueRequests -= 1
	if state.Alive then
		syncToggles()
	end
	if not sameCharacter(job) or state.Motion ~= motion then
		removeSoccerGlue(motion)
		return false
	end
	if not ok then
		error(problem, 0)
	end
	local deadline = os.clock() + 3
	while sameCharacter(job) and activeQuest() == 8 and os.clock() < deadline do
		if soccerConnected(motion.Glue, job.Root, ball) then
			break
		end
		task.wait(0.1)
	end
	if not sameCharacter(job) or state.Motion ~= motion then
		return false
	end
	if not soccerConnected(motion.Glue, job.Root, ball) then
		error("Glue appeared, but did not connect both the character and ball", 0)
	end
	motion.Ball, motion.BallCollision = ball, ball.CanCollide
	motion.BallOffset = job.Root.Position - ball.Position
	motion.BallLocal = job.Root.CFrame:PointToObjectSpace(ball.Position)
	ball.CanCollide = false
	local start = ball.Position
	state.Status = "Soccer: checking Glue connection"
	if not moveTo(job, CFrame.new(job.Root.Position + Vector3.new(0, 5, 0)), 1) then
		if sameCharacter(job) and activeQuest() == 8 then
			error("Soccer Glue lift failed", 0)
		end
		return false
	end
	if not pause(job, 0.5, true) then
		return false
	end
	if not ball.Parent or ball.Position.Y < start.Y + 2 or not soccerConnected(motion.Glue, job.Root, ball) then
		error("Glue connected, but the ball did not move with the character", 0)
	end
	return true
end
local function scoreSoccerGoal(job, root, ball)
	local goal = soccerGoal(root, ball)
	if not goal then
		return false
	end
	state.Status = "Soccer: scoring a goal"
	local kick = ball:FindFirstChild("KickRE")
	if kick and kick:IsA("RemoteEvent") then
		acquireMotion(job)
		for _ = 1, 3 do
			if not sameCharacter(job) or not ball:IsDescendantOf(root) or activeQuest() ~= 8 then
				break
			end
			local delta = goal.Frame.Position - ball.Position
			if delta.Magnitude < 0.1 then
				break
			end
			local direction = delta.Unit
			if not moveTo(job, CFrame.new(ball.Position - direction * 5), 1) then
				break
			end
			if (job.Root.Position - ball.Position).Magnitude < 9 then
				kick:FireServer((direction + Vector3.new(0, 0.15, 0)).Unit)
			end
			if waitQuestResult(job, 8, 2) then
				return true
			end
		end
	end
	if not sameCharacter(job) or activeQuest() ~= 8 then
		return false
	end
	if not attachSoccerBall(job, ball) then
		return false
	end
	local motion = state.Motion
	local delta = goal.Frame.Position - ball.Position
	local direction = if delta.Magnitude > 0.1 then delta.Unit else goal.Frame.LookVector
	local target = goal.Frame.Position + motion.BallOffset
	if not moveTo(job, CFrame.new(target), math.max(1, delta.Magnitude / 70)) then
		return questCompleted(8)
	end
	if not pause(job, 0.5, true) then
		return false
	end
	if
		goal.Part
		and goal.Part.Parent
		and (ball.Position - goal.Part.Position).Magnitude < goal.Part.Size.Magnitude / 2 + ball.Size.Magnitude / 2
	then
		pulseTouch(ball, goal.Part)
	end
	if waitQuestResult(job, 8, 2) then
		return true
	end
	moveTo(job, CFrame.new(target + direction * (ball.Size.Magnitude / 2 + 6)), 1)
	return waitQuestResult(job, 8, 2)
end
local function soccerToChest(job, ball)
	state.Status = "Soccer: carrying ball to the chest"
	if not state.Motion or state.Motion.Ball ~= ball then
		if not attachSoccerBall(job, ball) then
			error("Soccer ball attachment failed", 0)
		end
	end
	local motion = state.Motion
	local gates, trigger = getRoute()
	local height = math.max(cfg.Height, ball.Size.Y / 2 + 35) + motion.BallOffset.Y
	if not moveTo(job, CFrame.new(job.Root.Position.X, height, job.Root.Position.Z), 2) then
		return false
	end
	for index, gate in ipairs(gates) do
		state.Status = string.format("Soccer: carrying ball %d / %d", index, #gates)
		local target = Vector3.new(gate.Position.X, height, gate.Position.Z)
		if not moveTo(job, CFrame.new(target), (target - job.Root.Position).Magnitude / 100) then
			return questCompleted(8)
		end
	end
	local target = trigger.Position + motion.BallOffset
	if not moveTo(job, CFrame.new(target), (target - job.Root.Position).Magnitude / 100) then
		return questCompleted(8)
	end
	if not pause(job, 1, true) then
		return false
	end
	if (ball.Position - trigger.Position).Magnitude > 6 then
		return false
	end
	pulseTouch(ball, trigger)
	if waitQuestResult(job, 8, 2) then
		return true
	end
	if not sameCharacter(job) or activeQuest() ~= 8 then
		return false
	end
	removeSoccerGlue(motion)
	while current(job) and state.GlueRequests > 0 do
		task.wait(0.1)
	end
	if not sameCharacter(job) then
		return false
	end
	for _, connected in ipairs(job.Root:GetConnectedParts(true)) do
		if connected == ball then
			error("Ball is still attached after Glue removal", 0)
		end
	end
	ball.CanCollide = motion.BallCollision
	motion.Ball = nil
	if not state.ChestCharacters[job.Character] then
		state.ChestCharacters[job.Character] = true
		if moveTo(job, trigger.CFrame, 1) and sameCharacter(job) then
			pulseTouch(job.Root, trigger)
		end
	end
	return waitQuestResult(job, 8, 8)
end
local function assistSoccer(job)
	if not waitForCharacter(job, nil, 15) then
		error("Character unavailable", 0)
	end
	local root, ball
	local deadline = os.clock() + 8
	repeat
		if not sameCharacter(job) or activeQuest() ~= 8 then
			return
		end
		root = findPath(job.QuestZone, "Quest")
		ball = root and soccerBall(root)
		if ball then
			break
		end
		task.wait(0.25)
	until os.clock() >= deadline
	if not ball then
		error("No unambiguous movable quest ball found", 0)
	end
	local mode = cfg.SoccerMode
	local completed = false
	if mode ~= "End chest" then
		completed = scoreSoccerGoal(job, root, ball)
	end
	if not completed and mode ~= "Goal" and sameCharacter(job) and activeQuest() == 8 and ball:IsDescendantOf(root) then
		completed = soccerToChest(job, ball)
	end
	releaseMotion(job)
	if not current(job) then
		return
	end
	if not completed and not questCompleted(8) then
		error("Soccer route ended without a completion marker", 0)
	end
	state.Status = "Soccer completed"
end

local function autoQuests(job)
	if not teamLeader() then
		error("Only your team leader can start quests", 0)
	end
	local plot = zone()
	local supported = { [1] = true, [2] = true, [4] = type(fireclickdetector) == "function", [8] = true, [9] = true }
	local queue, completed, skipped = {}, 0, 0
	local active = activeQuest()
	if active ~= 0 then
		if not supported[active] then
			state.Status = "Finish the active quest first"
			return
		end
		table.insert(queue, active)
	end
	for _, id in ipairs({ 1, 2, 4, 8, 9 }) do
		if id ~= active and not questCompleted(id) then
			table.insert(queue, id)
		end
	end
	for _, id in ipairs(queue) do
		if not current(job) or zone() ~= plot or not teamLeader() then
			return
		end
		if not supported[id] or not questAvailable(id) then
			skipped += 1
			continue
		end
		job.QuestId, job.QuestZone = id, plot
		local owned = activeQuest() == id
		local ok, problem = pcall(function()
			if not owned then
				if not pause(job, math.max(0, state.QuestCooldown - os.clock())) then
					return
				end
				requestQuest(id)
				owned = true
				awaitQuest(job, id)
			end
			if not questCompleted(id) then
				if id == 9 then
					if not waitForCharacter(job, nil, 45) then
						error("Character unavailable", 0)
					end
					if state.ChestCharacters[job.Character] then
						local previous = job.Character
						job.Humanoid.Health = 0
						if not waitForCharacter(job, previous, 45) then
							error("Respawn timed out", 0)
						end
					end
					runTrip(job)
				elseif id == 8 then
					assistSoccer(job)
				else
					assistQuest(job, id)
				end
			end
		end)
		releaseMotion(job)
		while current(job) and state.GlueRequests > 0 do
			task.wait(0.1)
		end
		if current(job) and state.PendingGlue then
			error("Delete temporary Soccer Glue before continuing quests", 0)
		end
		if not current(job) or zone() ~= plot or not teamLeader() then
			return
		end
		if not questCompleted(id) and owned then
			if not pause(job, 2) then
				return
			end
		end
		if questCompleted(id) then
			completed += 1
		else
			skipped += 1
			if not ok then
				warn("[BABFT] Quest " .. id .. ": " .. tostring(problem))
			end
		end
		if activeQuest() ~= 0 then
			if not owned or activeQuest() ~= id then
				return
			end
			if not pause(job, math.max(0, state.QuestCooldown - os.clock())) then
				return
			end
			requestQuest(0)
			awaitQuest(job, 0)
		end
		if not pause(job, 2) then
			return
		end
	end
	state.Status = string.format("Quests: %d completed, %d skipped", completed, skipped)
end

local function beginJob(kind, callback)
	if not state.Alive then
		error("Controller unloaded", 0)
	end
	if state.Job or requestsPending() then
		error("Stop the current activity and wait for its pending build request", 0)
	end
	local job = { Kind = kind }
	state.Job = job
	syncToggles()
	job.Thread = task.defer(function()
		local ok, problem = xpcall(callback, debug.traceback, job)
		if current(job) then
			releaseMotion(job)
			state.Job = nil
			cfg.Farm = false
			syncToggles()
			if not ok then
				warn("[BABFT] " .. tostring(problem))
				state.Status = "Stopped"
			end
		end
	end)
end
local function guarded(callback)
	return function(...)
		if not state.Alive then
			return
		end
		local ok, problem = xpcall(callback, debug.traceback, ...)
		if not ok then
			warn("[BABFT] " .. tostring(problem))
		end
	end
end
local dashboardCore = {}
local function createDashboard()
	local gui = Instance.new("ScreenGui")
	ui.Dashboard = gui
	gui.Name = "BABFTFarmMonitor"
	gui.ResetOnSpawn = false
	gui.IgnoreGuiInset = true
	gui.DisplayOrder = 2147483645
	gui.Enabled = false
	gui.Parent = playerGui
	local backdrop = Instance.new("Frame")
	backdrop.Size = UDim2.fromScale(1, 1)
	backdrop.BackgroundColor3 = Color3.new(0, 0, 0)
	backdrop.BorderSizePixel = 0
	backdrop.Parent = gui
	local stats = Instance.new("Frame")
	stats.AnchorPoint = Vector2.new(0, 1)
	stats.Position = UDim2.new(0, 24, 1, -24)
	stats.Size = UDim2.new(1, -48, 0, 174)
	stats.BackgroundTransparency = 1
	stats.Parent = backdrop
	local function label(y, height, size, muted)
		local object = Instance.new("TextLabel")
		object.Position = UDim2.fromOffset(0, y)
		object.Size = UDim2.new(1, 0, 0, height)
		object.BackgroundTransparency = 1
		object.Font = Enum.Font.Gotham
		object.TextColor3 = if muted then Color3.fromRGB(130, 130, 130) else Color3.fromRGB(235, 235, 235)
		object.Text = ""
		object.TextSize = size
		object.TextXAlignment = Enum.TextXAlignment.Left
		object.TextYAlignment = Enum.TextYAlignment.Top
		object.Parent = stats
		return object
	end
	ui.DashboardStatus = label(0, 25, 16, false)
	ui.DashboardTime = label(29, 20, 12, true)
	ui.DashboardRates = label(59, 66, 15, false)
	ui.DashboardTotals = label(137, 34, 12, true)
	table.insert(
		state.Connections,
		UserInputService.InputBegan:Connect(function(input, processed)
			if input.KeyCode == Enum.KeyCode.F8 and not processed then
				setDashboard(not cfg.Dashboard)
			end
		end)
	)
end
setDashboard = function(enabled)
	cfg.Dashboard = enabled
	if ui.Dashboard then
		ui.Dashboard.Enabled = enabled
	end
	if enabled then
		for _, kind in ipairs(Enum.CoreGuiType:GetEnumItems()) do
			if kind ~= Enum.CoreGuiType.All and dashboardCore[kind] == nil then
				local ok, value = pcall(StarterGui.GetCoreGuiEnabled, StarterGui, kind)
				if ok then
					dashboardCore[kind] = value
					pcall(StarterGui.SetCoreGuiEnabled, StarterGui, kind, false)
				end
			end
		end
	else
		for kind, value in pairs(dashboardCore) do
			pcall(StarterGui.SetCoreGuiEnabled, StarterGui, kind, value)
		end
		table.clear(dashboardCore)
	end
	if ui.DashboardToggle then
		ui.DashboardToggle:Set(enabled, true)
	end
end
updateDashboard = function()
	local elapsed = math.max(1, os.clock() - state.Started)
	if ui.DashboardStatus then
		ui.DashboardStatus.Text = state.Status
		ui.DashboardTime.Text = string.format(
			"%02d:%02d:%02d",
			math.floor(elapsed / 3600),
			math.floor(elapsed / 60) % 60,
			math.floor(elapsed) % 60
		)
		ui.DashboardRates.Text = string.format(
			"%.1f gold / min\n%.0f gold / hour\n%.1f gold blocks / hour",
			state.GoldEarned * 60 / elapsed,
			state.GoldEarned * 3600 / elapsed,
			state.BlocksEarned * 3600 / elapsed
		)
		ui.DashboardTotals.Text = string.format(
			"+%d gold   /   +%d gold blocks\n%d runs   /   %d interrupted",
			state.GoldEarned,
			state.BlocksEarned,
			state.Arrivals,
			state.Deaths
		)
	end
end
local function buildUi()
	library = loadstring(game:HttpGet(UI_URL))()
	window = library.new({
		Name = "BABFT",
		Title = "BABFT",
		Parent = playerGui,
		Personal = false,
		AutoInitialize = false,
		AutoSave = false,
		LoadSettings = false,
	})
	ui.Window = window.Gui
	ui.Window.DisplayOrder = 2147483646
	window.Overview.Page:Destroy()
	window.Overview = nil
	local main = window:Page({ Name = "Main", Icon = "home" })
	local section = main:Card({ Title = "Farm", Collapsible = false })
	ui.Farm = section:Toggle({
		Name = "Autofarm",
		Persist = false,
		Callback = guarded(function(enabled)
			if syncingToggle then
				return
			end
			if not enabled then
				if state.Job and state.Job.Kind == "Farm" then
					stopJob("Stopped")
				end
				return
			end
			if state.Job or requestsPending() or activeQuest() ~= 0 then
				syncToggles()
				return
			end
			cfg.Farm = true
			beginJob("Farm", farm)
		end),
	})
	ui.DashboardToggle = section:Toggle({
		Name = "Farm monitor",
		Persist = false,
		Callback = guarded(function(enabled)
			if not syncingToggle then
				setDashboard(enabled)
			end
		end),
	})
	ui.Quests = section:Toggle({
		Name = "Auto complete quests",
		Persist = false,
		Callback = guarded(function(enabled)
			if syncingToggle then
				return
			end
			if not enabled then
				if state.Job and state.Job.Kind == "Quest" then
					stopJob("Stopped")
				end
				return
			end
			if state.Job or requestsPending() then
				syncToggles()
				return
			end
			beginJob("Quest", autoQuests)
		end),
	})
	section:Dropdown({
		Name = "Soccer route",
		Options = { "Goal then chest", "Goal", "End chest" },
		Default = cfg.SoccerMode,
		Persist = false,
		Callback = function(value)
			cfg.SoccerMode = value
		end,
	})
	ui.Activity = section:Label({ Name = "Idle", RawText = true })
	createDashboard()
	buildImporterUi(window)
	main:Select()
	table.insert(
		state.Connections,
		ui.Window.Destroying:Connect(function()
			state.Alive = false
			stopJob()
			setDashboard(false)
			if ui.Dashboard then
				ui.Dashboard:Destroy()
			end
			if ui.Browser then
				ui.Browser:Destroy()
			end
			if clearPreview then
				clearPreview()
			end
			for _, connection in ipairs(state.Connections) do
				connection:Disconnect()
			end
			for _, entry in pairs(state.ValueConnections) do
				entry.Connection:Disconnect()
			end
			table.clear(state.Connections)
			table.clear(state.ValueConnections)
		end)
	)
end

local importer = {
	Path = "",
	Rows = {},
	Created = {},
	Declared = {},
	Delay = 0.12,
	Pending = nil,
	Origin = nil,
	Plot = nil,
}
local function buildStatus(text)
	if state.Alive then
		state.Status = text
	end
end
local function finite(value)
	return type(value) == "number" and value == value and math.abs(value) < math.huge
end
local function buildVector(value, label)
	if type(value) ~= "string" then
		error(label .. " must be a comma-separated triple", 0)
	end
	local components = string.split(value, ",")
	if #components ~= 3 then
		error(label .. " must contain three numbers", 0)
	end
	for index, component in ipairs(components) do
		components[index] = tonumber(component)
		if not finite(components[index]) then
			error(label .. " contains an invalid number", 0)
		end
	end
	return Vector3.new(components[1], components[2], components[3])
end
local function buildArray(value, label)
	if type(value) ~= "table" then
		error(label .. " must be an array", 0)
	end
	local count = 0
	for key in pairs(value) do
		if not finite(key) or key % 1 ~= 0 or key < 1 or key > #value then
			error(label .. " is not a dense array", 0)
		end
		count += 1
	end
	if count ~= #value then
		error(label .. " has missing entries", 0)
	end
end
local function editableBuild()
	if requestsPending() or state.Job then
		error("Stop the current activity before changing the build plan", 0)
	end
	if importer.Pending or next(importer.Created) then
		error("This import has placed or pending blocks; resume it before changing the plan", 0)
	end
	importer.Origin, importer.Plot = nil, nil
end
local function buildTool(name, remoteName)
	local tool = findPath(player, "Backpack", name) or findPath(player.Character, name)
	local remote = findPath(tool, remoteName or "RF")
	if not remote or not remote:IsA("RemoteFunction") then
		error(name .. " is required for this import", 0)
	end
	return remote
end
local function buildProperties(record, material)
	local properties = {}
	for key, name in pairs({ ss = "Cast shadow", cc = "Collision" }) do
		if record[key] ~= nil then
			if type(record[key]) ~= "boolean" then
				error(material .. ": invalid " .. key, 0)
			end
			properties[name] = record[key]
		end
	end
	if record.a ~= nil and type(record.a) ~= "boolean" then
		error(material .. ": invalid anchored flag", 0)
	end
	if record.tr ~= nil then
		if not finite(record.tr) or record.tr < 0 or record.tr > 1 then
			error(material .. ": invalid transparency", 0)
		end
		properties.Transparency = record.tr
	end
	local unsupported = 0
	local numberNames = {
		Piston = { ExtendLength = "Piston length", Speed = "Piston speed" },
		Servo = { TargetAngle = "Servo angle" },
		Motor = { MaxSpeed = "Wheel speed" },
		JetTurbine = { MaxSpeed = "Jet speed" },
	}
	local booleanNames = { ReverseRotation = "Reverse rotation", ShowCrosshairs = "Crosshairs" }
	for _, field in ipairs({ "nv", "bv" }) do
		if record[field] ~= nil and type(record[field]) ~= "table" then
			error(material .. ": invalid " .. field, 0)
		end
		for key, value in pairs(record[field] or {}) do
			if (field == "nv" and not finite(value)) or (field == "bv" and type(value) ~= "boolean") then
				error(material .. ": invalid property " .. tostring(key), 0)
			end
			local name = if field == "nv" then (numberNames[material] or {})[key] else booleanNames[key]
			if name then
				properties[name] = value
			else
				unsupported += 1
			end
		end
	end
	if record.c ~= nil then
		if type(record.c) ~= "table" then
			error(material .. ": invalid constraint settings", 0)
		end
		for key, values in pairs(record.c) do
			if key == "hg" and (material == "Servo" or material == "Motor") then
				buildArray(values, "Hinge settings")
				if #values ~= 3 then
					error("Hinge settings must have three values", 0)
				end
				for _, value in ipairs(values) do
					if not finite(value) then
						error("Invalid hinge value", 0)
					end
				end
				local torque = values[if material == "Servo" then 2 else 1]
				if torque < 1e6 or torque > 1e10 then
					error("Unsupported hinge torque", 0)
				end
				local level = math.log10(torque / 1e6) + 1
				if math.abs(level - math.round(level)) > 0.001 then
					error("Hinge torque is not a supported level", 0)
				end
				properties[if material == "Servo" then "Servo torque" else "Wheel torque"] = math.round(level)
				if material == "Servo" then
					properties["Servo speed"] = values[3]
				end
			else
				unsupported += 1
			end
		end
	end
	if material == "JetTurbine" and record.nv and record.nv.MaxForce then
		local level = math.log10(record.nv.MaxForce / 1e6) + 1
		if not finite(level) or level < 1 or level > 5 or math.abs(level - math.round(level)) > 0.001 then
			error("Jet force is not a supported level", 0)
		end
		properties["Jet force"] = math.round(level)
		unsupported -= 1
	end
	return properties, unsupported
end
local function decodeBuild(source)
	if #source > 20 * 1024 * 1024 then
		error("Build file exceeds 20 MB", 0)
	end
	source = source:gsub("^\239\187\191", "")
	local body = source
	local declared = {}
	if not source:match("^%s*{") then
		if not source:match("^BuilderHub_") or not source:find("\nBH2", 1, true) then
			error("Unsupported build format", 0)
		end
		body = source:match("\nBuild%s*=%s*({.*})%s*$")
		if not body then
			error("BH2 Build section is missing", 0)
		end
		local header = source:match("\nBlocksNeeded%s*=%s*([^\r\n]+)")
		if header then
			local decoded = HttpService:JSONDecode(header)
			if type(decoded) ~= "table" or type(decoded.Blocks) ~= "table" then
				error("Invalid BlocksNeeded section", 0)
			end
			declared = decoded.Blocks
		end
	end
	local data = HttpService:JSONDecode(body)
	if type(data) ~= "table" or (data.v ~= 2 and data.v ~= 3) or type(data.b) ~= "table" then
		error("Only BH2 version 2 or 3 is supported", 0)
	end
	return data, declared
end
local function loadBuild()
	editableBuild()
	if importer.Path == "" then
		error("Select a build first", 0)
	end
	local data, declared = decodeBuild(readfile(importer.Path))
	local templates = findPath(game:GetService("ReplicatedStorage"), "BuildingParts")
	if not templates then
		error("Building templates are unavailable", 0)
	end
	local rows, ids, unresolved, unsupported = {}, {}, 0, 0
	for material, records in pairs(data.b) do
		if type(material) ~= "string" then
			error("Invalid material name", 0)
		end
		local template = templates:FindFirstChild(material)
		if not template or not template:IsA("Model") or not template.PrimaryPart then
			error("Unknown material: " .. material, 0)
		end
		if
			material == "Rope"
			or material == "Bar"
			or material == "Spring"
			or material == "Cut"
			or material:find("Tool")
		then
			error("Unsupported placement type: " .. material, 0)
		end
		buildArray(records, material)
		for _, record in ipairs(records) do
			if
				type(record) ~= "table"
				or not finite(record.i)
				or record.i % 1 ~= 0
				or record.i < 1
				or ids[record.i]
			then
				error("Missing or duplicate block ID in " .. material, 0)
			end
			ids[record.i] = true
			local position = buildVector(record.p, material .. " position")
			local rotation = buildVector(record.r, material .. " rotation")
			if position.Magnitude > 10000 or rotation.Magnitude > 1000000 then
				error("Build coordinates exceed supported bounds", 0)
			end
			local size = if record.sz then buildVector(record.sz, material .. " size") else template.PrimaryPart.Size
			if math.min(size.X, size.Y, size.Z) < 0.0499 or math.max(size.X, size.Y, size.Z) > 2048 then
				error("Invalid block size", 0)
			end
			if record.sz and not material:find("Block", 1, true) then
				error("Cannot resize " .. material, 0)
			end
			local color
			if record.cl then
				local rgb = buildVector(record.cl, material .. " color")
				if math.min(rgb.X, rgb.Y, rgb.Z) < 0 or math.max(rgb.X, rgb.Y, rgb.Z) > 1 then
					error("Invalid block color", 0)
				end
				color = Color3.new(rgb.X, rgb.Y, rgb.Z)
			end
			if record.tx ~= nil and (material ~= "Sign" or type(record.tx) ~= "string") then
				error("Invalid sign text", 0)
			end
			if record.bd ~= nil then
				buildArray(record.bd, "Bindings")
				unresolved += #record.bd
			end
			local properties, omitted = buildProperties(record, material)
			unsupported += omitted
			local baseSize = template.PrimaryPart.Size
			local cost = math.max(1, size.X * size.Y * size.Z / (baseSize.X * baseSize.Y * baseSize.Z))
			table.insert(rows, {
				Id = record.i,
				Material = material,
				Size = size,
				Color = color,
				Properties = properties,
				Text = record.tx,
				Cost = cost,
				BaseVolume = baseSize.X * baseSize.Y * baseSize.Z,
				Scaled = record.sz ~= nil,
				Frame = CFrame.new(position)
					* CFrame.Angles(math.rad(rotation.X), math.rad(rotation.Y), math.rad(rotation.Z)),
			})
			if #rows > 15000 then
				error("Build exceeds 15,000 records", 0)
			end
		end
	end
	if #rows == 0 then
		error("Build contains no blocks", 0)
	end
	for material, amount in pairs(declared) do
		if
			type(material) ~= "string"
			or not data.b[material]
			or not finite(amount)
			or amount < 0
			or amount % 1 ~= 0
		then
			error("Invalid declared inventory requirement", 0)
		end
	end
	table.sort(rows, function(a, b)
		return a.Id < b.Id
	end)
	importer.Rows, importer.Declared = rows, declared
	importer.Unresolved = unresolved
	buildStatus(string.format("Loaded %d blocks | %d unresolved bindings", #rows, unresolved))
	print(
		string.format(
			"[BABFT Builder] %s: %d blocks, XYZ Euler degrees, plot-relative placement; all imported blocks remain anchored",
			importer.Path,
			#rows
		)
	)
	if unresolved > 0 or unsupported > 0 then
		warn(
			string.format(
				"[BABFT Builder] %d binding links and %d unsupported settings will not be restored; this import does not reproduce working vehicle controls",
				unresolved,
				unsupported
			)
		)
	end
end
local function buildOrigin()
	if #importer.Rows == 0 then
		error("Load a build file first", 0)
	end
	local plot = zone()
	if not plot or not plot:IsA("BasePart") then
		error("Your building plot is unavailable", 0)
	end
	if importer.Plot and importer.Plot ~= plot then
		error("The import belongs to a different plot", 0)
	end
	return importer.Origin or plot.CFrame, plot
end
local function buildInventory()
	local needs, committed = {}, {}
	for _, row in ipairs(importer.Rows) do
		local entry = importer.Created[row.Id]
		local part = entry and entry.Model.Parent and entry.Model.PrimaryPart
		local consumed = if part then math.max(1, part.Size.X * part.Size.Y * part.Size.Z / row.BaseVolume) else 0
		committed[row.Material] = (committed[row.Material] or 0) + consumed
		needs[row.Material] = (needs[row.Material] or 0) + math.max(0, row.Cost - consumed)
	end
	for material, amount in pairs(importer.Declared) do
		needs[material] = math.max(needs[material] or 0, amount - (committed[material] or 0))
	end
	local missing = {}
	for material, amount in pairs(needs) do
		local data = findPath(player, "Data", material)
		local used = findPath(data, "Used")
		local available = if data and finite(data.Value)
			then data.Value - (if used and finite(used.Value) then used.Value else 0)
			else 0
		if available + 1e-4 < amount then
			table.insert(missing, material .. " (-" .. math.ceil(amount - available) .. ")")
		end
	end
	table.sort(missing)
	return missing
end
local function validateBuildBounds(origin, plot)
	for _, row in ipairs(importer.Rows) do
		local relative = plot.CFrame:ToObjectSpace(origin * row.Frame)
		for _, x in ipairs({ -1, 1 }) do
			for _, y in ipairs({ -1, 1 }) do
				for _, z in ipairs({ -1, 1 }) do
					local point = relative * (row.Size * Vector3.new(x, y, z) / 2)
					if math.abs(point.X) >= plot.Size.X / 2 or math.abs(point.Z) >= plot.Size.Z / 2 then
						error("Block " .. row.Id .. " extends outside your plot; the model is too large", 0)
					end
				end
			end
		end
	end
end
local function closeBuildFrame(actual, expected)
	return (actual.Position - expected.Position).Magnitude <= 0.1
		and actual.RightVector:Dot(expected.RightVector) >= 0.999
		and actual.UpVector:Dot(expected.UpVector) >= 0.999
end
local function reconcileBuildPlacement()
	local pending = importer.Pending
	if not pending then
		return
	end
	local deadline = os.clock() + 12
	repeat
		local match
		for _, model in ipairs(pending.Folder:GetChildren()) do
			if
				not pending.Before[model]
				and model:IsA("Model")
				and model.Name == pending.Row.Material
				and model.PrimaryPart
				and closeBuildFrame(model.PrimaryPart.CFrame, pending.Frame)
			then
				if match then
					error(
						"Multiple new blocks match pending ID " .. pending.Row.Id .. "; placement cannot be attributed",
						0
					)
				end
				match = model
			end
		end
		if match then
			importer.Created[pending.Row.Id] = { Model = match, Done = false }
			importer.Pending = nil
			return
		end
		task.wait(0.15)
	until os.clock() >= deadline
	error(
		"Placement #" .. pending.Row.Id .. " is unconfirmed; resume only retries observation, never places it twice",
		0
	)
end
local function buildCurrent(job)
	if not current(job) then
		return false
	end
	if zone() ~= importer.Plot or activeQuest() ~= 0 then
		error("Plot or quest changed during import", 0)
	end
	local launched = findPath(importer.Plot, "Launched")
	if launched and launched.Value then
		error("The plot launched during import", 0)
	end
	return true
end
local function waitBuildValue(job, predicate)
	local deadline = os.clock() + 3
	repeat
		if not buildCurrent(job) then
			return false
		end
		if predicate() then
			return true
		end
		task.wait(0.1)
	until os.clock() >= deadline
	error("The server did not confirm an imported block setting", 0)
end
local function setBuildProperty(job, model, descriptor, desired, remote)
	local function equal(value)
		return if type(desired) == "number"
			then type(value) == "number" and math.abs(value - desired) < 0.001
			else value == desired
	end
	for _ = 1, 5 do
		if not buildCurrent(job) then
			return false
		end
		local previous = descriptor.GetState(model)
		if equal(previous) then
			return true
		end
		if descriptor.ValueType == "number+" then
			local text = if descriptor.Name == "Transparency" then tostring(desired * 100) .. "%" else tostring(desired)
			remote:InvokeServer(descriptor.Name, { model }, text)
		else
			remote:InvokeServer(descriptor.Name, { model })
		end
		if not waitBuildValue(job, function()
			return descriptor.GetState(model) ~= previous
		end) then
			return false
		end
	end
	if not equal(descriptor.GetState(model)) then
		error("Cannot restore property " .. descriptor.Name, 0)
	end
	return true
end
local function applyBuildRow(job, row, entry, tools, descriptors)
	local model, frame = entry.Model, importer.Origin * row.Frame
	if not model.Parent or not model.PrimaryPart then
		error("Imported block #" .. row.Id .. " was removed", 0)
	end
	if not buildCurrent(job) then
		return false
	end
	if not model.PrimaryPart.Anchored then
		error("Imported blocks must stay anchored while resuming", 0)
	end
	if
		row.Scaled
		and (
			(model.PrimaryPart.Size - row.Size).Magnitude > 0.015
			or not closeBuildFrame(model.PrimaryPart.CFrame, frame)
		)
	then
		tools.Scale:InvokeServer(model, row.Size, frame)
		if
			not waitBuildValue(job, function()
				return model.PrimaryPart
					and (model.PrimaryPart.Size - row.Size).Magnitude <= 0.015
					and closeBuildFrame(model.PrimaryPart.CFrame, frame)
			end)
		then
			return false
		end
	end
	if not closeBuildFrame(model.PrimaryPart.CFrame, frame) then
		error("Imported block #" .. row.Id .. " moved; stop editing while importing", 0)
	end
	if
		row.Color
		and (
				Vector3.new(model.PrimaryPart.Color.R, model.PrimaryPart.Color.G, model.PrimaryPart.Color.B)
				- Vector3.new(row.Color.R, row.Color.G, row.Color.B)
			).Magnitude
			> 0.008
	then
		if not buildCurrent(job) then
			return false
		end
		tools.Paint:InvokeServer({ { model.PrimaryPart, row.Color } })
		if
			not waitBuildValue(job, function()
				local color = model.PrimaryPart.Color
				return math.abs(color.R - row.Color.R)
						+ math.abs(color.G - row.Color.G)
						+ math.abs(color.B - row.Color.B)
					< 0.015
			end)
		then
			return false
		end
	end
	for name, value in pairs(row.Properties) do
		local descriptor = (descriptors[row.Material] or {})[name] or descriptors.All[name]
		if not setBuildProperty(job, model, descriptor, value, tools.Properties) then
			return false
		end
	end
	if row.Text and not entry.TextSent then
		if not buildCurrent(job) then
			return false
		end
		local remote = findPath(model, "ClickDetector", "Script", "UpdateSignRE")
		if not remote or not remote:IsA("RemoteEvent") then
			error("Sign text remote is unavailable", 0)
		end
		remote:FireServer(row.Text)
		entry.TextSent = true
	end
	return buildCurrent(job)
end
local function runBuild(job)
	if importer.Pending then
		reconcileBuildPlacement()
	end
	if not current(job) then
		return
	end
	local origin, plot = buildOrigin()
	if activeQuest() ~= 0 then
		error("Finish or cancel the active quest before importing", 0)
	end
	local launched = findPath(plot, "Launched")
	if launched and launched.Value then
		error("Return to an unlaunched plot before importing", 0)
	end
	local folder = findPath(Workspace, "Blocks", player.Name)
	if not folder then
		error("Your block folder is unavailable", 0)
	end
	local missing = buildInventory()
	if #missing > 0 then
		error("Missing inventory: " .. table.concat(missing, ", "), 0)
	end
	validateBuildBounds(origin, plot)
	local tools = { Build = buildTool("BuildingTool") }
	local module = findPath(game:GetService("ReplicatedStorage"), "Scripts", "BlockProperties")
	if not module then
		error("Block property definitions are unavailable", 0)
	end
	local descriptors = require(module)
	for _, row in ipairs(importer.Rows) do
		if row.Scaled then
			tools.Scale = tools.Scale or buildTool("ScalingTool")
		end
		if row.Color then
			tools.Paint = tools.Paint or buildTool("PaintingTool")
		end
		if next(row.Properties) then
			tools.Properties = tools.Properties or buildTool("PropertiesTool", "SetPropertieRF")
		end
		for name, value in pairs(row.Properties) do
			local descriptor = (descriptors[row.Material] or {})[name] or descriptors.All[name]
			if not descriptor or type(descriptor.GetState) ~= "function" then
				error(row.Material .. " does not support " .. name, 0)
			end
			if
				type(value) == "number"
				and ((descriptor.Min and value < descriptor.Min) or (descriptor.Max and value > descriptor.Max))
			then
				error(row.Material .. " has an unsupported " .. name .. " value", 0)
			end
		end
	end
	importer.Origin, importer.Plot = origin, plot
	for index, row in ipairs(importer.Rows) do
		if not buildCurrent(job) then
			return
		end
		local entry = importer.Created[row.Id]
		if entry and (entry.Model.Parent ~= folder or not entry.Model.PrimaryPart) then
			error("Imported block #" .. row.Id .. " is missing", 0)
		end
		if
			entry
			and entry.Done
			and (
				not entry.Model.PrimaryPart.Anchored
				or not closeBuildFrame(entry.Model.PrimaryPart.CFrame, origin * row.Frame)
			)
		then
			error("Imported block #" .. row.Id .. " changed after import; keep the build anchored and in place", 0)
		end
		if not entry then
			local data = findPath(player, "Data", row.Material)
			if not data or not finite(data.Value) then
				error("Inventory changed before placement", 0)
			end
			local before = {}
			for _, model in ipairs(folder:GetChildren()) do
				before[model] = true
			end
			local frame = origin * row.Frame
			importer.Pending = { Row = row, Before = before, Folder = folder, Frame = frame }
			buildStatus(string.format("Placing %d/%d: %s", index, #importer.Rows, row.Material))
			local ok, problem = pcall(function()
				tools.Build:InvokeServer(
					row.Material,
					data.Value,
					plot,
					plot.CFrame:ToObjectSpace(frame),
					true,
					frame,
					false,
					false
				)
			end)
			reconcileBuildPlacement()
			if not ok then
				warn(
					"[BABFT Builder] Placement response failed, but a matching block was observed: "
						.. tostring(problem)
				)
			end
			entry = importer.Created[row.Id]
		end
		if not entry.Done then
			if not applyBuildRow(job, row, entry, tools, descriptors) then
				return
			end
			entry.Done = true
		end
		buildStatus(
			string.format(
				"Imported %d/%d | anchored | %d unbound links",
				index,
				#importer.Rows,
				importer.Unresolved or 0
			)
		)
		if not pause(job, importer.Delay) then
			return
		end
	end
	buildStatus(
		string.format(
			"Geometry/settings complete: %d blocks | %d unbound links",
			#importer.Rows,
			importer.Unresolved or 0
		)
	)
end
local browser = {
	Mode = "Workspace",
	Query = "",
	Files = {},
	Results = {},
	Metadata = {},
	Page = 1,
	Generation = 0,
	Render = 0,
	Selecting = false,
}
local function basename(path)
	return path:gsub("\\", "/"):match("([^/]+)$") or path
end
local function cacheKey(path)
	local hash = 5381
	for index = 1, #path do
		hash = (hash * 33 + string.byte(path, index)) % 4294967296
	end
	return string.format("%08x", hash)
end
local function buildRequest(endpoint)
	local send = request or http_request
	if type(send) ~= "function" then
		error("HTTP requests are unavailable", 0)
	end
	local response = send({
		Url = "https://chatgptqq.com/api/builds" .. endpoint,
		Method = "GET",
		Headers = { ["ngrok-skip-browser-warning"] = "true", Referer = "https://babft.com/", Accept = "*/*" },
	})
	if type(response) ~= "table" or type(response.Body) ~= "string" then
		error("Empty catalog response", 0)
	end
	local status = tonumber(response.StatusCode)
	if not status or status < 200 or status >= 300 then
		error("Catalog HTTP " .. tostring(status), 0)
	end
	if #response.Body > 20 * 1024 * 1024 then
		error("Catalog response is too large", 0)
	end
	return response.Body
end
local function storageFolders()
	if type(makefolder) ~= "function" or type(isfolder) ~= "function" or type(writefile) ~= "function" then
		error("Workspace writing is unavailable", 0)
	end
	for _, path in ipairs({ "BABFT", "BABFT/Downloads", "BABFT/Images" }) do
		if not isfolder(path) then
			makefolder(path)
		end
	end
end
local function buildInfo(source)
	local data, declared = decodeBuild(source)
	local count, links, materialCount = 0, 0, 0
	local materials = {}
	for material, records in pairs(data.b) do
		if type(material) ~= "string" then
			error("Invalid material name", 0)
		end
		buildArray(records, material)
		materialCount += 1
		for _, record in ipairs(records) do
			if type(record) ~= "table" then
				error("Invalid block record", 0)
			end
			count += 1
			if count > 15000 then
				error("Build exceeds 15,000 records", 0)
			end
			if type(record.bd) == "table" then
				links += #record.bd
			end
		end
		local amount = declared[material]
		materials[material] = if finite(amount) and amount >= 0 then amount else #records
	end
	local list = {}
	for material, amount in pairs(materials) do
		table.insert(list, material .. ": " .. tostring(amount))
	end
	table.sort(list)
	return {
		Count = count,
		Summary = string.format(
			"BH2 v%d  |  %d parts  |  %d materials  |  %.1f KB",
			data.v,
			count,
			materialCount,
			#source / 1024
		),
		Details = table.concat(list, "\n")
			.. (if links > 0 then "\n\n" .. links .. " binding links (not restored)" else ""),
	}
end
clearPreview = function()
	if state.BuildPreview then
		state.BuildPreview:Destroy()
		state.BuildPreview = nil
	end
end
local function previewBuild()
	if state.Job or requestsPending() or browser.Selecting then
		return
	end
	if #importer.Rows == 0 then
		loadBuild()
	end
	local origin = buildOrigin()
	clearPreview()
	local model = Instance.new("Model")
	model.Name = "BABFTPreview"
	state.BuildPreview = model
	model.Parent = Workspace
	for index, row in ipairs(importer.Rows) do
		if not state.Alive or state.BuildPreview ~= model then
			return
		end
		local part = Instance.new("Part")
		part.Name = row.Material
		part.Anchored, part.CanCollide, part.CanTouch, part.CanQuery = true, false, false, false
		part.CastShadow = false
		part.Size, part.CFrame = row.Size, origin * row.Frame
		part.Color = row.Color or Color3.fromRGB(72, 187, 200)
		part.Transparency = 0.45
		part.Parent = model
		if index % 100 == 0 then
			task.wait()
		end
	end
end
local function selectedInfo(info)
	ui.Selected:Set(basename(importer.Path))
	ui.BuildInfo:Set(info.Summary)
	ui.Materials.Text = info.Details
end
local function selectBuild(path)
	if state.Job or requestsPending() or importer.Pending then
		return false
	end
	local info = buildInfo(readfile(path))
	if path ~= importer.Path then
		clearPreview()
		importer.Rows, importer.Created, importer.Declared = {}, {}, {}
		importer.Origin, importer.Plot, importer.Unresolved = nil, nil, nil
	end
	importer.Path = path
	browser.Metadata[path] = info
	selectedInfo(info)
	if ui.Browser then
		ui.Browser.Enabled = false
		browser.Render += 1
	end
	return true
end
local renderBrowser, refreshBrowser
local function browserStatus(text)
	if state.Alive and ui.BrowserStatus then
		ui.BrowserStatus.Text = text
	end
end
local function scanWorkspace(generation)
	if type(listfiles) ~= "function" or type(isfolder) ~= "function" or type(readfile) ~= "function" then
		error("Workspace scanning needs listfiles, isfolder and readfile", 0)
	end
	local folders, visited, files = { "." }, {}, {}
	local index, seen, failed = 1, 0, 0
	while index <= #folders and seen < 10000 do
		if not state.Alive or generation ~= browser.Generation then
			return nil
		end
		local folder = folders[index]
		index += 1
		local ok, paths = pcall(listfiles, folder)
		if ok and type(paths) == "table" then
			for _, path in ipairs(paths) do
				if type(path) ~= "string" then
					continue
				end
				local key = path:gsub("\\", "/"):gsub("^%./", ""):gsub("/+$", ""):lower()
				if visited[key] then
					continue
				end
				visited[key] = true
				seen += 1
				if seen > 10000 then
					break
				end
				if isfolder(path) then
					if #folders < 1000 and not key:match("babft/images$") then
						table.insert(folders, path)
					end
				elseif key:sub(-6) == ".build" then
					table.insert(files, { Path = path, Name = basename(path) })
				end
			end
		else
			failed += 1
		end
		task.wait()
	end
	table.sort(files, function(a, b)
		return a.Path:lower() < b.Path:lower()
	end)
	return files, (seen >= 10000 or #folders >= 1000), failed
end
local function filteredFiles()
	local results, query = {}, browser.Query:lower()
	for _, file in ipairs(browser.Files) do
		if query == "" or file.Name:lower():find(query, 1, true) then
			table.insert(results, file)
		end
	end
	return results
end
local function imageAsset(path)
	if type(getcustomasset) ~= "function" or type(isfile) ~= "function" then
		return nil
	end
	storageFolders()
	local stem = "BABFT/Images/" .. cacheKey(path)
	for _, extension in ipairs({ ".png", ".jpg", ".webp" }) do
		if isfile(stem .. extension) then
			return getcustomasset(stem .. extension)
		end
	end
	local body = buildRequest("/image?path=" .. HttpService:UrlEncode(path))
	local extension
	if body:sub(1, 8) == "\137PNG\r\n\26\n" then
		extension = ".png"
	elseif body:sub(1, 3) == "\255\216\255" then
		extension = ".jpg"
	elseif body:sub(1, 4) == "RIFF" and body:sub(9, 12) == "WEBP" then
		extension = ".webp"
	else
		return nil
	end
	writefile(stem .. extension, body)
	return getcustomasset(stem .. extension)
end
local function downloadBuild(file)
	if browser.Selecting or state.Job or requestsPending() or importer.Pending then
		browserStatus("Stop the current activity before selecting a build")
		return
	end
	browser.Selecting = true
	browserStatus("Downloading " .. file.Name)
	local ok, problem = pcall(function()
		storageFolders()
		local source = buildRequest("/download?path=" .. HttpService:UrlEncode(file.Path))
		local info = buildInfo(source)
		if not state.Alive then
			return
		end
		local name = basename(file.Name):gsub('[%c<>:"/\\|?*]', "_"):sub(1, 140)
		if name:lower():sub(-6) ~= ".build" then
			name ..= ".Build"
		end
		local path = "BABFT/Downloads/" .. cacheKey(file.Path) .. "_" .. name
		if type(isfile) ~= "function" then
			error("isfile is unavailable", 0)
		end
		if isfile(path) and readfile(path) ~= source then
			local index = 1
			local stem = path:sub(1, -7)
			repeat
				path = stem .. "_" .. index .. ".Build"
				index += 1
			until not isfile(path)
		end
		if not isfile(path) then
			writefile(path, source)
		end
		if selectBuild(path) then
			browserStatus("Selected " .. basename(path) .. "  |  " .. info.Summary)
		end
	end)
	browser.Selecting = false
	if not ok then
		browserStatus(tostring(problem))
	end
end
local function browserObject(class, parent, properties)
	local object = Instance.new(class)
	for key, value in pairs(properties) do
		object[key] = value
	end
	object.Parent = parent
	return object
end
local function browserText(parent, text, position, size, fontSize)
	return browserObject("TextLabel", parent, {
		Position = position,
		Size = size,
		BackgroundTransparency = 1,
		Text = text,
		TextColor3 = Color3.fromRGB(225, 232, 243),
		TextSize = fontSize or 14,
		Font = Enum.Font.GothamMedium,
		TextWrapped = true,
		TextXAlignment = Enum.TextXAlignment.Left,
	})
end
local function browserButton(parent, text, position, size, callback)
	local button = browserObject("TextButton", parent, {
		Position = position,
		Size = size,
		BackgroundColor3 = Color3.fromRGB(34, 83, 87),
		BorderSizePixel = 0,
		Text = text,
		TextColor3 = Color3.new(1, 1, 1),
		TextSize = 14,
		Font = Enum.Font.GothamMedium,
	})
	Instance.new("UICorner", button).CornerRadius = UDim.new(0, 7)
	button.Activated:Connect(guarded(callback))
	return button
end
renderBrowser = function()
	browser.Render += 1
	local render = browser.Render
	for _, child in ipairs(ui.BrowserGrid:GetChildren()) do
		if child:IsA("Frame") then
			child:Destroy()
		end
	end
	local pages = math.max(1, math.ceil(#browser.Results / 6))
	browser.Page = math.clamp(browser.Page, 1, pages)
	ui.BrowserPage.Text = string.format("%d / %d  ·  %d builds", browser.Page, pages, #browser.Results)
	ui.LocalTab.BackgroundColor3 = if browser.Mode == "Workspace"
		then Color3.fromRGB(34, 115, 105)
		else Color3.fromRGB(34, 44, 59)
	ui.OnlineTab.BackgroundColor3 = if browser.Mode == "Online"
		then Color3.fromRGB(34, 115, 105)
		else Color3.fromRGB(34, 44, 59)
	local images, locals = {}, {}
	for index = (browser.Page - 1) * 6 + 1, math.min(browser.Page * 6, #browser.Results) do
		local file = browser.Results[index]
		local card = browserObject("Frame", ui.BrowserGrid, {
			BackgroundColor3 = Color3.fromRGB(25, 33, 46),
			BorderSizePixel = 0,
			LayoutOrder = index,
		})
		Instance.new("UICorner", card).CornerRadius = UDim.new(0, 10)
		local fallback = browserText(
			card,
			if file.Online then "Image unavailable" else ".Build",
			UDim2.fromOffset(12, 8),
			UDim2.new(1, -24, 1, -100),
			18
		)
		fallback.TextXAlignment = Enum.TextXAlignment.Center
		local thumb = browserObject("ImageLabel", card, {
			Position = UDim2.fromOffset(8, 8),
			Size = UDim2.new(1, -16, 1, -100),
			BackgroundTransparency = 1,
			Image = "",
			ScaleType = Enum.ScaleType.Fit,
		})
		browserText(card, file.Name, UDim2.new(0, 12, 1, -88), UDim2.new(1, -24, 0, 42), 12)
		browserButton(
			card,
			if file.Online then "Download & select" else "Select",
			UDim2.new(0, 10, 1, -38),
			UDim2.new(1, -20, 0, 28),
			function()
				if file.Online then
					downloadBuild(file)
					return
				end
				if browser.Selecting then
					return
				end
				local ok, result = pcall(selectBuild, file.Path)
				if not ok then
					browserStatus(tostring(result))
				elseif result then
					browserStatus("Selected " .. file.Name .. "  |  " .. browser.Metadata[file.Path].Summary)
				else
					browserStatus("Stop the current activity before selecting a build")
				end
			end
		)
		if file.Online and file.HasImage then
			table.insert(images, { File = file, Image = thumb, Fallback = fallback })
		elseif not file.Online then
			table.insert(locals, { File = file, Label = fallback })
		end
	end
	task.spawn(function()
		for _, entry in ipairs(locals) do
			if not state.Alive or render ~= browser.Render then
				return
			end
			local info = browser.Metadata[entry.File.Path]
			if not info then
				local ok, result = pcall(function()
					return buildInfo(readfile(entry.File.Path))
				end)
				info = if ok
					then result
					else { Summary = "Unsupported or unreadable build", Details = tostring(result) }
				browser.Metadata[entry.File.Path] = info
			end
			if render == browser.Render and entry.Label.Parent then
				entry.Label.Text = info.Summary
			end
			task.wait()
		end
	end)
	task.spawn(function()
		for _, entry in ipairs(images) do
			if not state.Alive or render ~= browser.Render or not ui.Browser.Enabled then
				return
			end
			local ok, asset = pcall(imageAsset, entry.File.Path)
			if ok and asset and state.Alive and render == browser.Render and entry.Image.Parent then
				entry.Image.Image = asset
				entry.Fallback.Text = ""
				entry.Image:GetPropertyChangedSignal("IsLoaded"):Connect(function()
					if entry.Image.IsLoaded then
						entry.Fallback.Text = ""
					end
				end)
				task.delay(5, function()
					if entry.Image.Parent and not entry.Image.IsLoaded then
						entry.Fallback.Text = "Image format unavailable"
					end
				end)
			end
		end
	end)
end
refreshBrowser = function()
	browser.Generation += 1
	browser.Render += 1
	local generation, mode, query = browser.Generation, browser.Mode, browser.Query
	browserStatus(if mode == "Online" then "Searching babft.com…" else "Scanning workspace…")
	local ok, problem = pcall(function()
		local results, note
		if mode == "Workspace" then
			local files, limited, failed = scanWorkspace(generation)
			if not files then
				return
			end
			if generation ~= browser.Generation then
				return
			end
			browser.Files, browser.Metadata = files, {}
			results = filteredFiles()
			note = if limited
				then "Scan limit reached"
				elseif failed > 0 then "Some folders could not be read"
				else "Workspace"
		else
			local response = HttpService:JSONDecode(buildRequest("?q=" .. HttpService:UrlEncode(query)))
			if type(response) ~= "table" or type(response.builds) ~= "table" then
				error("Invalid build catalog", 0)
			end
			results = {}
			for _, file in ipairs(response.builds) do
				if type(file) == "table" and type(file.path) == "string" and type(file.filename) == "string" then
					table.insert(
						results,
						{ Path = file.path, Name = file.filename, Online = true, HasImage = file.has_image == true }
					)
				end
				if #results >= 10000 then
					break
				end
			end
			note = "babft.com"
		end
		if not state.Alive or generation ~= browser.Generation then
			return
		end
		browser.Results, browser.Page = results, 1
		renderBrowser()
		browserStatus(note .. "  |  " .. #results .. " builds")
	end)
	if not ok and generation == browser.Generation then
		browserStatus(tostring(problem))
	end
end
local function createBrowser()
	local gui = browserObject("ScreenGui", playerGui, {
		Name = "BABFTBuildBrowser",
		ResetOnSpawn = false,
		IgnoreGuiInset = true,
		DisplayOrder = 2147483647,
		Enabled = false,
		ZIndexBehavior = Enum.ZIndexBehavior.Sibling,
	})
	ui.Browser = gui
	local shade = browserObject("TextButton", gui, {
		Size = UDim2.fromScale(1, 1),
		BackgroundColor3 = Color3.new(),
		BackgroundTransparency = 0.3,
		Text = "",
		Modal = true,
		AutoButtonColor = false,
		BorderSizePixel = 0,
	})
	local panel = browserObject("Frame", shade, {
		AnchorPoint = Vector2.new(0.5, 0.5),
		Position = UDim2.fromScale(0.5, 0.5),
		Size = UDim2.new(1, -32, 1, -32),
		BackgroundColor3 = Color3.fromRGB(14, 19, 27),
		BorderSizePixel = 0,
	})
	browserObject("UISizeConstraint", panel, { MaxSize = Vector2.new(1050, 740) })
	Instance.new("UICorner", panel).CornerRadius = UDim.new(0, 14)
	browserText(panel, "Build browser", UDim2.fromOffset(20, 12), UDim2.new(1, -90, 0, 34), 24)
	browserButton(panel, "X", UDim2.new(1, -50, 0, 12), UDim2.fromOffset(32, 32), function()
		gui.Enabled = false
		browser.Render += 1
	end)
	ui.LocalTab = browserButton(panel, "Workspace", UDim2.fromOffset(20, 58), UDim2.fromOffset(116, 32), function()
		browser.Mode = "Workspace"
		refreshBrowser()
	end)
	ui.OnlineTab = browserButton(panel, "Online", UDim2.fromOffset(144, 58), UDim2.fromOffset(92, 32), function()
		browser.Mode = "Online"
		refreshBrowser()
	end)
	local search = browserObject("TextBox", panel, {
		Position = UDim2.fromOffset(20, 100),
		Size = UDim2.new(1, -140, 0, 36),
		Text = "",
		PlaceholderText = "Search builds…",
		ClearTextOnFocus = false,
		TextSize = 15,
		Font = Enum.Font.Gotham,
		TextColor3 = Color3.new(1, 1, 1),
		BackgroundColor3 = Color3.fromRGB(25, 33, 46),
		BorderSizePixel = 0,
	})
	Instance.new("UICorner", search).CornerRadius = UDim.new(0, 8)
	local function searchFiles()
		browser.Query = search.Text
		refreshBrowser()
	end
	search.FocusLost:Connect(function(enter)
		if enter then
			searchFiles()
		end
	end)
	browserButton(panel, "Search", UDim2.new(1, -110, 0, 100), UDim2.fromOffset(90, 36), searchFiles)
	browserButton(panel, "Refresh", UDim2.new(1, -110, 0, 58), UDim2.fromOffset(90, 32), searchFiles)
	ui.BrowserGrid = browserObject("ScrollingFrame", panel, {
		Position = UDim2.fromOffset(20, 150),
		Size = UDim2.new(1, -40, 1, -236),
		BackgroundTransparency = 1,
		BorderSizePixel = 0,
		ScrollBarThickness = 3,
		CanvasSize = UDim2.new(),
		AutomaticCanvasSize = Enum.AutomaticSize.Y,
	})
	local grid = browserObject("UIGridLayout", ui.BrowserGrid, {
		CellSize = UDim2.new(1 / 3, -8, 0, 218),
		CellPadding = UDim2.fromOffset(10, 10),
		SortOrder = Enum.SortOrder.LayoutOrder,
	})
	local function resize()
		local columns = if ui.BrowserGrid.AbsoluteSize.X < 600 then 2 else 3
		grid.CellSize = UDim2.new(1 / columns, -8, 0, 218)
	end
	ui.BrowserGrid:GetPropertyChangedSignal("AbsoluteSize"):Connect(resize)
	ui.BrowserPage = browserText(panel, "", UDim2.new(0, 72, 1, -76), UDim2.new(1, -144, 0, 28), 14)
	ui.BrowserPage.TextXAlignment = Enum.TextXAlignment.Center
	browserButton(panel, "<", UDim2.new(0, 20, 1, -76), UDim2.fromOffset(42, 28), function()
		browser.Page -= 1
		renderBrowser()
	end)
	browserButton(panel, ">", UDim2.new(1, -62, 1, -76), UDim2.fromOffset(42, 28), function()
		browser.Page += 1
		renderBrowser()
	end)
	ui.BrowserStatus = browserText(panel, "", UDim2.new(0, 20, 1, -42), UDim2.new(1, -40, 0, 34), 12)
end
openBrowser = function()
	if not ui.Browser then
		createBrowser()
	end
	ui.Browser.Enabled = true
	refreshBrowser()
end
buildImporterUi = function(builderWindow)
	local page = builderWindow:Page({ Name = "Builder", Icon = "hammer" })
	local section = page:Card({ Title = "Builds", Collapsible = false })
	section:Button({ Name = "Browse builds", Callback = guarded(openBrowser) })
	ui.Selected = section:Label({ Name = "Select a build", RawText = true })
	ui.BuildInfo = section:Label({ Name = "Workspace files and babft.com", RawText = true })
	ui.Preview = section:Button({
		Name = "Preview build",
		Callback = guarded(function()
			local ok, problem = pcall(previewBuild)
			if not ok then
				clearPreview()
				warn("[BABFT] " .. tostring(problem))
			end
		end),
	})
	section:Button({ Name = "Clear preview", Callback = guarded(clearPreview) })
	ui.Build = section:Button({
		Name = "Build / Resume",
		Callback = guarded(function()
			if state.Job or requestsPending() or browser.Selecting then
				return
			end
			local loaded, problem = pcall(function()
				if #importer.Rows == 0 then
					loadBuild()
				end
			end)
			if not loaded then
				warn("[BABFT] " .. tostring(problem))
				return
			end
			clearPreview()
			beginJob("Build", function(job)
				state.BuildBusy = true
				local ok, failure = xpcall(runBuild, debug.traceback, job)
				state.BuildBusy = false
				syncToggles()
				if not ok then
					state.Status = "Import stopped"
					warn("[BABFT] " .. tostring(failure))
				elseif not current(job) then
					state.Status = "Import stopped"
				end
			end)
		end),
	})
	ui.StopBuild = section:Button({
		Name = "Stop build",
		Disabled = true,
		Callback = guarded(function()
			if state.Job and state.Job.Kind == "Build" then
				stopJob("Import stopped")
			end
		end),
	})

	ui.BuilderStatus = section:Label({ Name = "Idle", RawText = true })
	local materials = page:Card({ Title = "Materials", Collapsible = true })
	local detail = materials:Custom({ Name = "Inventory", AutomaticSize = true, Height = 0, Searchable = false })
	ui.Materials = browserText(detail.Body, "", UDim2.new(), UDim2.new(1, 0, 0, 0), 12)
	ui.Materials.TextYAlignment = Enum.TextYAlignment.Top
	ui.Materials.AutomaticSize = Enum.AutomaticSize.Y
end

local function watchCounter(name, totalKey)
	local value = findPath(player, "Data", name)
	local previous = state.ValueConnections[name]
	if previous and previous.Object == value then
		return
	end
	if previous then
		previous.Connection:Disconnect()
		state.ValueConnections[name] = nil
	end
	if not value or not value:IsA("ValueBase") or type(value.Value) ~= "number" then
		return
	end
	local last = value.Value
	state.ValueConnections[name] = {
		Object = value,
		Connection = value.Changed:Connect(function(amount)
			if state.Alive and type(amount) == "number" then
				state[totalKey] += math.max(0, amount - last)
				last = amount
			end
		end),
	}
end

local ok, problem = xpcall(buildUi, debug.traceback)
if not ok then
	warn("[BABFT] " .. tostring(problem))
	state.Alive = false
	stopJob()
	setDashboard(false)
	if ui.Window then
		window:Destroy()
	end
	if ui.Dashboard then
		ui.Dashboard:Destroy()
	end
	for _, connection in ipairs(state.Connections) do
		connection:Disconnect()
	end
	return
end
watchCounter("Gold", "GoldEarned")
watchCounter("GoldBlock", "BlocksEarned")
task.spawn(function()
	while state.Alive do
		local updated, failure = pcall(function()
			local pending = state.PendingGlue
			if
				pending
				and not pending.GlueDeleting
				and (not pending.Glue or pending.Glue.Parent ~= pending.GlueFolder)
			then
				pending.Glue = nil
				state.PendingGlue = nil
				syncToggles()
			end
			watchCounter("Gold", "GoldEarned")
			watchCounter("GoldBlock", "BlocksEarned")
			claimResults()
			updateDashboard()
			if ui.Activity then
				ui.Activity:Set(state.Status)
			end
			if ui.BuilderStatus then
				ui.BuilderStatus:Set(state.Status)
			end
		end)
		if not updated then
			warn("[BABFT] " .. tostring(failure))
			stopJob("Stopped")
			break
		end
		task.wait(1)
	end
end)

Features & Information

Auto farm gold Auto quest Auto Build
#BABFT#autofarm gold#best#script#build a boat for treasure

Community Comments (12)

turbotruster0turbotruster0Apr 22, 2022(edited)

an actual working one thx

32tq32tqApr 25, 2022(edited)

@turbotruster0 you are welcome

egon5005egon5005May 21, 2022(edited)

cool auto farm

eightglint73825eightglint73825Jun 26, 2022(edited)

sometimes when i hit the chest i just stay there and dont movve

32tq32tqJun 26, 2022(edited)

it will use firetouchinterest to collect gold

casey200711ccasey200711cJul 9, 2022(edited)

WELL MADE THX

crimspar0crimspar0Jul 14, 2022(edited)

like it more so that the community knows it works

neirouzkhalef3neirouzkhalef3Jul 18, 2022(edited)

Why it kicks me out of the game?

FreyaaFreyaaAug 7, 2022(edited)

when i get in the chest ¿it bugs

ForeverekkForeverekkAug 20, 2022(edited)

cool

fatihxbox1221fatihxbox1221Jan 27, 2025(edited)

W script

altvenderaltvenderAug 18, 2025

hey bro your script is old it dosent launch it just directly goes and does not even collect gold please fix it ,thanks

More Scripts for Build A Boat For Treasure

AutoQuest AutoFarm AutoBuild — Roblox Script | KocakZ