Semi-automatic build
Key Required

Semi-automatic build

GEF6day136day13 1.4K views 1.9K executesUpdated Sep 14, 2026
Unrated
00
Lua Script•772 lines (35763 chars)
--[[ loadstring(game:HttpGet("https://gist.githubusercontent.com/hasanhluong-stack/13e9a9fd47f7e90e8e12127f23714967/raw/7472a0f6f6352af5ece95290ac58adc7ae9a7dea/Script"))() ]]

local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local TweenService = game:GetService("TweenService")

local player = Players.LocalPlayer
local playerGui = player:WaitForChild("PlayerGui")

-- ================= Biến lưu trữ dữ liệu & Trạng thái =================
local lastTarget1, lastTarget2, lastDir
local currentVec1, currentVec2 = Vector3.zero, Vector3.zero

local listenState = 0 
local tempStart1, tempStart2, tempEnd1, tempEnd2

local inputsLeft, inputsRight = {}, {}

local settingsData = {
	repeatCount = 1,
	buildDir = Vector3.new(0, 1, 0),
	thickness = 1,
	buildDelay = 0.05,
	baseSize = "1x1",
	baseLayers = 2,
	baseOffset = 0.5,
	pillarHeight = 25,
	wallHeight = 10,
	wallLength = 5,
	autoSnapYaw = false,
}

local snapYawConnection = nil

-- ================= Hàm hỗ trợ Làm tròn & Utility =================
local function round2(num)
	return num and math.floor(num * 100 + 0.5) / 100 or 0
end

local function roundVec2(vec)
	return Vector3.new(round2(vec.X), round2(vec.Y), round2(vec.Z))
end

local function formatNumStr(num)
	return string.format("%.2f", round2(num))
end

local function formatTextBoxTo2Decimals(textBox)
	local num = tonumber(textBox.Text)
	if num then textBox.Text = formatNumStr(num) end
end

local function setVectorToInputs(inputs, vec, startIndex)
	startIndex = startIndex or 1
	inputs[startIndex].Text = formatNumStr(vec.X)
	inputs[startIndex + 1].Text = formatNumStr(vec.Y)
	inputs[startIndex + 2].Text = formatNumStr(vec.Z)
end

local function parseVector3(str, defaultVec)
	if not str or str == "" then return defaultVec end
	local coords = {}
	for num in string.gmatch(str, "[^,]+") do
		local p = tonumber(num)
		if p then table.insert(coords, round2(p)) end
	end
	return #coords >= 3 and Vector3.new(coords[1], coords[2], coords[3]) or defaultVec
end

-- Hàm tạo UI Element nhanh để tránh lặp code Instance
local function create(className, props)
	local inst = Instance.new(className)
	for k, v in pairs(props) do
		inst[k] = v
	end
	return inst
end

-- ================= ESP 4 vị trí ván =================
local plankESPFolder = create("Folder", { Name = "PlankPositionESP", Parent = workspace })
local plankESPParts = {}

local function sameVector(a, b)
	return a and b and a.X == b.X and a.Y == b.Y and a.Z == b.Z
end

local function destroyPlankMarker(number)
	if plankESPParts[number] and plankESPParts[number].part then
		plankESPParts[number].part:Destroy()
	end
	plankESPParts[number] = nil
end

local function createPlankMarker(number, position)
	position = roundVec2(position)
	local existing = plankESPParts[number]
	if existing and existing.part and existing.part.Parent and sameVector(existing.position, position) then
		return
	end

	destroyPlankMarker(number)

	local part = create("Part", {
		Name = "ESP_Number_Part_" .. number,
		Anchored = true, CanCollide = false, CanTouch = false, CanQuery = false,
		Transparency = 1, Size = Vector3.new(0.2, 0.2, 0.2),
		CFrame = CFrame.new(position), Parent = plankESPFolder
	})

	local billboard = create("BillboardGui", {
		Name = "ESP_Number_" .. number, Adornee = part, AlwaysOnTop = true,
		LightInfluence = 0, MaxDistance = math.huge, Size = UDim2.fromOffset(42, 42),
		StudsOffsetWorldSpace = Vector3.new(0, 0.15, 0), Enabled = true, Parent = part
	})

	local label = create("TextLabel", {
		Name = "Number", BackgroundTransparency = 1, BorderSizePixel = 0,
		Size = UDim2.fromScale(1, 1), Font = Enum.Font.GothamBlack,
		Text = tostring(number), TextColor3 = Color3.fromRGB(255, 220, 60),
		TextSize = 23, TextStrokeColor3 = Color3.fromRGB(0, 0, 0), TextStrokeTransparency = 0, Parent = billboard
	})

	plankESPParts[number] = { part = part, billboard = billboard, label = label, position = position }
end

local function getInputVector(inputs, startIndex)
	if not inputs then return nil end
	local x, y, z = tonumber(inputs[startIndex] and inputs[startIndex].Text), tonumber(inputs[startIndex+1] and inputs[startIndex+1].Text), tonumber(inputs[startIndex+2] and inputs[startIndex+2].Text)
	return (x and y and z) and roundVec2(Vector3.new(x, y, z)) or nil
end

local function refreshESPFromInputs()
	local positions = {
		getInputVector(inputsLeft, 1),  getInputVector(inputsLeft, 4),
		getInputVector(inputsRight, 1), getInputVector(inputsRight, 4),
	}

	for i = 1, 4 do
		local pos = positions[i]
		if pos then
			local existing = plankESPParts[i]
			if existing and existing.part and existing.part.Parent then
				if not sameVector(existing.position, pos) then
					existing.part.CFrame = CFrame.new(pos)
					existing.position = pos
				end
			else
				createPlankMarker(i, pos)
			end
		else
			destroyPlankMarker(i)
		end
	end
end

-- ================= Hiệu ứng & Màu sắc =================
local TWEEN_FAST = TweenInfo.new(0.12, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)

local function tw(instance, tweenInfo, properties)
	local tween = TweenService:Create(instance, tweenInfo, properties)
	tween:Play()
	return tween
end

local THEME = { border = Color3.fromRGB(64, 71, 92), accentA = Color3.fromRGB(86, 163, 255) }
local COLOR = {
	ink = Color3.fromRGB(20, 23, 31), surface = Color3.fromRGB(30, 34, 48), surface2 = Color3.fromRGB(38, 43, 61),
	border = Color3.fromRGB(58, 64, 85), text = Color3.fromRGB(242, 239, 234), textMuted = Color3.fromRGB(144, 152, 176),
	accent = Color3.fromRGB(232, 163, 61), stepBg = Color3.fromRGB(48, 54, 76), listening = Color3.fromRGB(200, 120, 30),
	baseplateBg = Color3.fromRGB(60, 110, 80), pillarBg = Color3.fromRGB(120, 70, 160), wallBg = Color3.fromRGB(180, 80, 60),
	settingsBg = Color3.fromRGB(25, 28, 40), offRed = Color3.fromRGB(160, 50, 50), onGreen = Color3.fromRGB(50, 160, 70),
}

local function addPress(button)
	local scale = create("UIScale", { Parent = button })
	local function animate(input, targetScale)
		if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
			tw(scale, TWEEN_FAST, { Scale = targetScale })
		end
	end
	button.InputBegan:Connect(function(input) animate(input, 0.94) end)
	button.InputEnded:Connect(function(input) animate(input, 1) end)
end

-- ================= ScreenGui & Panels =================
local screenGui = create("ScreenGui", { Name = "FloatingPanelGui", ResetOnSpawn = false, ZIndexBehavior = Enum.ZIndexBehavior.Sibling, Parent = playerGui })

local panel = create("Frame", {
	Name = "Panel", AnchorPoint = Vector2.new(0.5, 0.5), Position = UDim2.fromScale(0.4, 0.5),
	Size = UDim2.fromOffset(480, 340), BackgroundColor3 = COLOR.surface, BorderSizePixel = 0,
	Visible = false, ZIndex = 10, Parent = screenGui
})
create("UICorner", { CornerRadius = UDim.new(0, 10), Parent = panel })
create("UIStroke", { Color = COLOR.border, Thickness = 1, Parent = panel })
create("UIPadding", { PaddingTop = UDim.new(0, 10), PaddingBottom = UDim.new(0, 10), PaddingLeft = UDim.new(0, 12), PaddingRight = UDim.new(0, 12), Parent = panel })

local title = create("TextLabel", {
	Name = "TitleHeader", BackgroundTransparency = 1, Size = UDim2.new(1, 0, 0, 20),
	Font = Enum.Font.GothamBold, TextSize = 12, TextColor3 = COLOR.textMuted,
	TextXAlignment = Enum.TextXAlignment.Left, Text = "BuildPlank Auto Builder (Hold to Drag)", ZIndex = 11, Parent = panel
})

-- Side GUI Settings
local settingsPanel = create("Frame", {
	Name = "SettingsPanel", Size = UDim2.new(0, 240, 1, 0), Position = UDim2.new(1, 10, 0, 0),
	BackgroundColor3 = COLOR.settingsBg, BorderSizePixel = 0, ZIndex = 10, Parent = panel
})
create("UICorner", { CornerRadius = UDim.new(0, 10), Parent = settingsPanel })
create("UIStroke", { Color = COLOR.border, Thickness = 1, Parent = settingsPanel })
create("UIPadding", { PaddingTop = UDim.new(0, 10), PaddingBottom = UDim.new(0, 10), PaddingLeft = UDim.new(0, 10), PaddingRight = UDim.new(0, 10), Parent = settingsPanel })

create("TextLabel", {
	BackgroundTransparency = 1, Size = UDim2.new(1, 0, 0, 20), Font = Enum.Font.GothamBold,
	TextSize = 13, TextColor3 = COLOR.accent, TextXAlignment = Enum.TextXAlignment.Left,
	Text = "⚙ Settings", ZIndex = 11, Parent = settingsPanel
})

local settingsContent = create("ScrollingFrame", {
	BackgroundTransparency = 1, Size = UDim2.new(1, 0, 1, -25), Position = UDim2.new(0, 0, 0, 25),
	CanvasSize = UDim2.new(0, 0, 0, 500), ScrollBarThickness = 3, ScrollBarImageColor3 = COLOR.accent,
	BorderSizePixel = 0, ZIndex = 11, Parent = settingsPanel
})
create("UIListLayout", { FillDirection = Enum.FillDirection.Vertical, Padding = UDim.new(0, 6), SortOrder = Enum.SortOrder.LayoutOrder, Parent = settingsContent })

local function createPairRow(layoutOrder)
	local row = create("Frame", { BackgroundTransparency = 1, Size = UDim2.new(1, -6, 0, 42), LayoutOrder = layoutOrder, ZIndex = 11, Parent = settingsContent })
	create("UIListLayout", { FillDirection = Enum.FillDirection.Horizontal, Padding = UDim.new(0, 8), SortOrder = Enum.SortOrder.LayoutOrder, Parent = row })
	return row
end

local function createSingleSettingItem(parent, labelText, defaultVal, widthScale, layoutOrder)
	local container = create("Frame", { BackgroundTransparency = 1, Size = UDim2.new(widthScale or 0.48, -4, 1, 0), LayoutOrder = layoutOrder, ZIndex = 11, Parent = parent })
	create("TextLabel", { BackgroundTransparency = 1, Size = UDim2.new(1, 0, 0, 14), Font = Enum.Font.GothamMedium, TextSize = 9, TextColor3 = COLOR.textMuted, TextXAlignment = Enum.TextXAlignment.Left, Text = labelText, ZIndex = 11, Parent = container })
	
	local boxFrame = create("Frame", { Size = UDim2.new(1, 0, 0, 24), Position = UDim2.new(0, 0, 0, 16), BackgroundColor3 = COLOR.surface2, BorderSizePixel = 0, ZIndex = 11, Parent = container })
	create("UICorner", { CornerRadius = UDim.new(0, 5), Parent = boxFrame })
	create("UIStroke", { Color = COLOR.border, Thickness = 1, Parent = boxFrame })

	return create("TextBox", { BackgroundTransparency = 1, Size = UDim2.fromScale(1, 1), Font = Enum.Font.Gotham, TextSize = 10, TextColor3 = COLOR.text, Text = defaultVal, ClearTextOnFocus = false, ZIndex = 12, Parent = boxFrame })
end

local function createToggleButton(parent, labelText, defaultState, layoutOrder)
	local container = create("Frame", { BackgroundTransparency = 1, Size = UDim2.new(1, -6, 0, 42), LayoutOrder = layoutOrder or 6, ZIndex = 11, Parent = parent })
	create("TextLabel", { BackgroundTransparency = 1, Size = UDim2.new(1, 0, 0, 14), Font = Enum.Font.GothamMedium, TextSize = 9, TextColor3 = COLOR.textMuted, TextXAlignment = Enum.TextXAlignment.Left, Text = labelText, ZIndex = 11, Parent = container })

	local btn = create("TextButton", {
		Size = UDim2.new(1, 0, 0, 24), Position = UDim2.new(0, 0, 0, 16),
		BackgroundColor3 = defaultState and COLOR.onGreen or COLOR.offRed, Font = Enum.Font.GothamBold,
		TextSize = 10, TextColor3 = COLOR.text, Text = defaultState and "Snap Yaw: ON" or "Snap Yaw: OFF", ZIndex = 11, Parent = container
	})
	create("UICorner", { CornerRadius = UDim.new(0, 5), Parent = btn })
	create("UIStroke", { Color = COLOR.border, Thickness = 1, Parent = btn })
	return btn
end

-- Generates Settings UI Rows
local row1, row2, row3, row4, row5 = createPairRow(1), createPairRow(2), createPairRow(3), createPairRow(4), createPairRow(5)
local setLoopInput        = createSingleSettingItem(row1, "Loop Count:", "1", 0.5)
local setThickInput       = createSingleSettingItem(row1, "Thickness (1-10):", "1", 0.5)
local setSpeedInput       = createSingleSettingItem(row2, "Speed (Delay):", "0.05", 0.5)
local setDirInput         = createSingleSettingItem(row2, "Vector (Dir):", "0, 1, 0", 0.5)
local setBaseSizeInput    = createSingleSettingItem(row3, "Base Size:", "1x1", 0.5)
local setBaseLayerInput   = createSingleSettingItem(row3, "Base Layers:", "2", 0.5)
local setBaseOffsetInput  = createSingleSettingItem(row4, "Base Offset:", "0.5", 0.5)
local setPillarHeightInput= createSingleSettingItem(row4, "Pillar Height:", "25", 0.5)
local setWallHeightInput  = createSingleSettingItem(row5, "Wall Height (x0.5):", "10", 0.5)
local setWallLengthInput  = createSingleSettingItem(row5, "Wall Length:", "5", 0.5)
local snapYawBtn          = createToggleButton(settingsContent, "Snap Character (90°):", false, 6)

local function updateSettingsFromUI()
	settingsData.repeatCount  = tonumber(setLoopInput.Text) or 1
	settingsData.buildDir     = parseVector3(setDirInput.Text, Vector3.new(0, 1, 0))
	settingsData.thickness    = math.clamp(math.floor(tonumber(setThickInput.Text) or 1), 1, 10)
	settingsData.buildDelay   = math.clamp(tonumber(setSpeedInput.Text) or 0.05, 0.001, 1)
	settingsData.baseSize     = setBaseSizeInput.Text ~= "" and setBaseSizeInput.Text or "1x1"
	settingsData.baseLayers   = math.clamp(tonumber(setBaseLayerInput.Text) or 2, 1, 2)
	settingsData.baseOffset   = tonumber(setBaseOffsetInput.Text) or 0.5
	settingsData.pillarHeight = tonumber(setPillarHeightInput.Text) or 25
	settingsData.wallHeight   = tonumber(setWallHeightInput.Text) or 10
	settingsData.wallLength   = tonumber(setWallLengthInput.Text) or 5
end

for _, input in ipairs({setLoopInput, setDirInput, setThickInput, setSpeedInput, setBaseSizeInput, setBaseLayerInput, setBaseOffsetInput, setPillarHeightInput, setWallHeightInput, setWallLengthInput}) do
	input.FocusLost:Connect(updateSettingsFromUI)
end

-- Logic Snap Yaw
snapYawBtn.MouseButton1Click:Connect(function()
	settingsData.autoSnapYaw = not settingsData.autoSnapYaw
	snapYawBtn.BackgroundColor3 = settingsData.autoSnapYaw and COLOR.onGreen or COLOR.offRed
	snapYawBtn.Text = settingsData.autoSnapYaw and "Snap Yaw: ON" or "Snap Yaw: OFF"
	
	if snapYawConnection then snapYawConnection:Disconnect() snapYawConnection = nil end
	if settingsData.autoSnapYaw then
		snapYawConnection = RunService.RenderStepped:Connect(function()
			local hrp = player.Character and player.Character:FindFirstChild("HumanoidRootPart")
			if hrp then
				local yaw = math.deg(math.atan2(-hrp.CFrame.LookVector.X, -hrp.CFrame.LookVector.Z))
				hrp.CFrame = CFrame.new(hrp.Position) * CFrame.Angles(0, math.rad(math.round(yaw / 90) * 90), 0)
			end
		end)
	end
end)

-- Drag Logic cho Main Panel
local panelDragging, panelDragStart, panelStartPos
title.InputBegan:Connect(function(input)
	if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
		panelDragging, panelDragStart, panelStartPos = true, input.Position, panel.Position
		input.Changed:Connect(function()
			if input.UserInputState == Enum.UserInputState.End then panelDragging = false end
		end)
	end
end)

UserInputService.InputChanged:Connect(function(input)
	if panelDragging and (input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch) then
		local delta = input.Position - panelDragStart
		panel.Position = UDim2.new(panelStartPos.X.Scale, panelStartPos.X.Offset + delta.X, panelStartPos.Y.Scale, panelStartPos.Y.Offset + delta.Y)
	end
end)

-- Inputs Column Generation
local function createSlotInput(parent, layoutOrder, placeholderText)
	local slot = create("Frame", { Name = "Slot", Size = UDim2.new(1, 0, 0, 30), LayoutOrder = layoutOrder, BackgroundColor3 = COLOR.surface2, BorderSizePixel = 0, ZIndex = 11, Parent = parent })
	create("UICorner", { CornerRadius = UDim.new(0, 6), Parent = slot })
	create("UIStroke", { Color = COLOR.border, Thickness = 1, Parent = slot })

	local textBox = create("TextBox", {
		Name = "Input", BackgroundTransparency = 1, Size = UDim2.fromScale(1, 1),
		Font = Enum.Font.Gotham, TextSize = 11, TextColor3 = COLOR.text,
		PlaceholderColor3 = COLOR.textMuted, PlaceholderText = placeholderText, Text = "",
		ClearTextOnFocus = false, ZIndex = 12, Parent = slot
	})
	textBox.FocusLost:Connect(function() formatTextBoxTo2Decimals(textBox) end)
	return textBox
end

local function createColumn6(labelText, xScale, storageTable)
	local column = create("Frame", { BackgroundTransparency = 1, Size = UDim2.new(0.48, 0, 0, 240), Position = UDim2.new(xScale, 0, 0, 25), ZIndex = 11, Parent = panel })
	create("UIListLayout", { FillDirection = Enum.FillDirection.Vertical, Padding = UDim.new(0, 4), SortOrder = Enum.SortOrder.LayoutOrder, Parent = column })
	create("TextLabel", { BackgroundTransparency = 1, Size = UDim2.new(1, 0, 0, 14), LayoutOrder = 0, Font = Enum.Font.GothamBold, TextSize = 11, TextColor3 = COLOR.accent, TextXAlignment = Enum.TextXAlignment.Left, Text = labelText, Parent = column })

	local placeholders = { "Start X", "Start Y", "Start Z", "End X", "End Y", "End Z" }
	for i = 1, 6 do
		if i == 4 then
			create("TextLabel", { BackgroundTransparency = 1, Size = UDim2.new(1, 0, 0, 12), LayoutOrder = 3.5, Font = Enum.Font.GothamMedium, TextSize = 10, TextColor3 = COLOR.textMuted, TextXAlignment = Enum.TextXAlignment.Left, Text = "TO:", Parent = column })
		end
		storageTable[i] = createSlotInput(column, i <= 3 and i or i + 1, placeholders[i])
	end
end

createColumn6("Plank 1 (Left)", 0, inputsLeft)
createColumn6("Plank 2 (Right)", 0.52, inputsRight)

refreshESPFromInputs()
RunService.RenderStepped:Connect(refreshESPFromInputs)

-- ================= Bottom Action Buttons =================
local bottomFrame = create("Frame", { BackgroundTransparency = 1, Size = UDim2.new(1, 0, 0, 32), Position = UDim2.new(0, 0, 1, -32), ZIndex = 11, Parent = panel })
create("UIListLayout", { FillDirection = Enum.FillDirection.Horizontal, Padding = UDim.new(0, 4), SortOrder = Enum.SortOrder.LayoutOrder, Parent = bottomFrame })

local function createBottomBtn(text, sizeScale, bgCol, textCol, order)
	local btn = create("TextButton", { Size = UDim2.new(sizeScale, 0, 1, 0), LayoutOrder = order, BackgroundColor3 = bgCol, Font = Enum.Font.GothamBold, TextSize = 9, TextColor3 = textCol, Text = text, ZIndex = 11, Parent = bottomFrame })
	create("UICorner", { CornerRadius = UDim.new(0, 6), Parent = btn })
	return btn
end

local updateButton   = createBottomBtn("Update", 0.16, COLOR.accent, COLOR.ink, 1)

local actionButton   = create("Frame", { Size = UDim2.new(0.24, 0, 1, 0), LayoutOrder = 2, BackgroundColor3 = COLOR.stepBg, BorderSizePixel = 0, ZIndex = 11, Parent = bottomFrame })
create("UICorner", { CornerRadius = UDim.new(0, 6), Parent = actionButton })
create("UIStroke", { ApplyStrokeMode = Enum.ApplyStrokeMode.Border, Color = COLOR.border, Thickness = 1, Transparency = 0.15, Parent = actionButton })

local actionSingleButton = create("TextButton", { Size = UDim2.fromScale(1, 1), BackgroundTransparency = 1, BorderSizePixel = 0, AutoButtonColor = false, Font = Enum.Font.GothamBold, TextSize = 9, TextColor3 = COLOR.text, Text = "Record Size", ZIndex = 12, Parent = actionButton })
local actionUndoButton   = create("TextButton", { Size = UDim2.new(0.5, 0, 1, 0), BackgroundColor3 = COLOR.stepBg, BorderSizePixel = 0, AutoButtonColor = false, Font = Enum.Font.GothamBold, TextSize = 9, TextColor3 = COLOR.text, Text = "Undo", ZIndex = 12, Visible = false, Parent = actionButton })
local actionContinueButton = create("TextButton", { Size = UDim2.new(0.5, 0, 1, 0), Position = UDim2.new(0.5, 0, 0, 0), BackgroundColor3 = COLOR.accent, BorderSizePixel = 0, AutoButtonColor = false, Font = Enum.Font.GothamBold, TextSize = 9, TextColor3 = COLOR.ink, Text = "Continue", ZIndex = 12, Visible = false, Parent = actionButton })
create("UICorner", { CornerRadius = UDim.new(0, 6), Parent = actionUndoButton })
create("UICorner", { CornerRadius = UDim.new(0, 6), Parent = actionContinueButton })

local actionDivider = create("Frame", { Size = UDim2.new(0, 1, 1, -6), Position = UDim2.new(0.5, 0, 0, 3), BackgroundColor3 = COLOR.border, BackgroundTransparency = 0.35, BorderSizePixel = 0, ZIndex = 13, Visible = false, Parent = actionButton })

local function setActionSingle(text, backgroundColor, textColor)
	actionSingleButton.Visible = true
	actionSingleButton.Text = text or ""
	actionSingleButton.TextColor3 = textColor or COLOR.text
	actionButton.BackgroundColor3 = backgroundColor or COLOR.stepBg
	actionUndoButton.Visible, actionContinueButton.Visible, actionDivider.Visible = false, false, false
end

local function setActionSplit(backgroundLeft, backgroundRight, continueText)
	actionSingleButton.Visible = false
	actionUndoButton.Visible, actionContinueButton.Visible, actionDivider.Visible = true, true, true
	actionUndoButton.BackgroundColor3 = backgroundLeft or COLOR.stepBg
	actionContinueButton.BackgroundColor3 = backgroundRight or COLOR.accent
	actionContinueButton.Text = continueText or "Continue"
	actionButton.BackgroundColor3 = backgroundLeft or COLOR.stepBg
end

local baseplateButton = createBottomBtn("Baseplate", 0.2, COLOR.baseplateBg, COLOR.text, 3)
local pillarButton    = createBottomBtn("Build Pillar", 0.18, COLOR.pillarBg, COLOR.text, 4)
local wallButton      = createBottomBtn("Build Wall", 0.18, COLOR.wallBg, COLOR.text, 5)

-- ================= Metamethod Hook =================
local oldNamecall
oldNamecall = hookmetamethod(game, "__namecall", newcclosure(function(self, ...)
	local method = getnamecallmethod()
	local args = {...}

	if (method == "FireServer" or method == "InvokeServer") and self.Name == "BuildPlank" then
		if #args >= 2 and typeof(args[1]) == "Vector3" and typeof(args[2]) == "Vector3" then
			local v1, v2 = roundVec2(args[1]), roundVec2(args[2])
			currentVec1, currentVec2 = v1, v2

			setVectorToInputs(inputsLeft, v1, 1)
			setVectorToInputs(inputsRight, v2, 1)

			if #args == 5 then
				lastTarget1, lastTarget2, lastDir = args[3], args[4], args[5]
			else
				lastDir = args[3]
			end

			if listenState == 1 then
				tempStart1, tempStart2 = v1, v2
				listenState = 2
				setActionSplit(COLOR.surface2, COLOR.accent, "Continue")
			elseif listenState == 3 then
				tempEnd1, tempEnd2 = v1, v2
				local altEnd = (tempEnd1 + tempEnd2) * 0.5
				setVectorToInputs(inputsLeft, tempStart1, 4)
				setVectorToInputs(inputsRight, altEnd, 4)
				listenState = 4
				setActionSplit(COLOR.surface2, COLOR.accent, "Create")
			end

			args[1], args[2] = v1, v2
		end
	end
	return oldNamecall(self, unpack(args))
end))

-- ================= Build Executions =================
local function ensureBuildContext(hammer, event, timeout)
	if lastTarget1 and lastTarget2 then return true end
	timeout = timeout or 1.0
	pcall(function()
		if hammer and hammer:IsA("Tool") then hammer:Activate() end
	end)
	local deadline = os.clock() + timeout
	while os.clock() < deadline do
		if lastTarget1 and lastTarget2 then return true end
		task.wait()
	end
	return lastTarget1 ~= nil and lastTarget2 ~= nil
end

local function executeBuild(start1, end1, start2, end2, callback)
	updateSettingsFromUI()
	local char = player.Character
	local hammer = char and char:FindFirstChild("Hammer")
	local event = hammer and hammer:FindFirstChild("BuildPlank")

	if not event or (not lastTarget1 and not ensureBuildContext(hammer, event, 1.0)) then
		if callback then callback(false, not event and "No Hammer!" or "BuildPlank context not ready!") end
		return
	end

	local diff1, diff2 = end1 - start1, end2 - start2
	local maxDist = math.max(math.abs(diff1.X), math.abs(diff1.Y), math.abs(diff1.Z), math.abs(diff2.X), math.abs(diff2.Y), math.abs(diff2.Z))
	local steps = math.max(1, math.floor((maxDist / 0.5) + 0.5))
	local finalDir = settingsData.buildDir or lastDir or Vector3.new(0, 1, 0)

	task.spawn(function()
		for _ = 1, settingsData.repeatCount do
			local prevP1, prevP2
			for i = 0, steps do
				local alpha = i / steps
				local curr1, curr2 = roundVec2(start1:Lerp(end1, alpha)), roundVec2(start2:Lerp(end2, alpha))

				local function firePlank(p1, p2)
					if lastTarget1 and lastTarget2 then
						event:FireServer(p1, p2, lastTarget1, lastTarget2, finalDir)
						return true
					end
					return false
				end

				if not firePlank(curr1, curr2) then
					if callback then callback(false, "BuildPlank context lost!") end
					return
				end

				local thickness = math.clamp(math.floor(settingsData.thickness or 1), 1, 10)
				if thickness >= 2 and prevP1 and prevP2 then
					for t = 1, thickness - 1 do
						local ratio = t / thickness
						firePlank(roundVec2(prevP1:Lerp(curr1, ratio)), roundVec2(prevP2:Lerp(curr2, ratio)))
					end
				end

				prevP1, prevP2 = curr1, curr2
				task.wait(settingsData.buildDelay)
			end
			task.wait(0.05)
		end
		if callback then callback(true) end
	end)
end

-- Helper lấy phẳng Character Look/Right Vector
local function getCharOrientation(hrp)
	local rawLook = hrp.CFrame.LookVector
	local flatLook = Vector3.new(rawLook.X, 0, rawLook.Z)
	flatLook = flatLook.Magnitude < 0.001 and Vector3.new(0, 0, -1) or flatLook.Unit
	return flatLook, Vector3.new(-flatLook.Z, 0, flatLook.X)
end

-- Helper kiểm tra Nhân vật
local function getHrpAndHumanoid(btn, defaultText)
	local char = player.Character
	local hrp, humanoid = char and char:FindFirstChild("HumanoidRootPart"), char and char:FindFirstChildOfClass("Humanoid")
	if not hrp or not humanoid then
		btn.Text = "No Char!"
		task.delay(1, function() btn.Text = defaultText end)
		return nil, nil
	end
	return hrp, humanoid
end

-- ================= Logic các Nút Xây =================
updateButton.MouseButton1Click:Connect(function()
	for i = 1, 6 do
		formatTextBoxTo2Decimals(inputsLeft[i])
		formatTextBoxTo2Decimals(inputsRight[i])
	end

	local start1 = Vector3.new(tonumber(inputsLeft[1].Text) or 0, tonumber(inputsLeft[2].Text) or 0, tonumber(inputsLeft[3].Text) or 0)
	local end1   = Vector3.new(tonumber(inputsLeft[4].Text) or start1.X, tonumber(inputsLeft[5].Text) or start1.Y, tonumber(inputsLeft[6].Text) or start1.Z)
	local start2 = Vector3.new(tonumber(inputsRight[1].Text) or 0, tonumber(inputsRight[2].Text) or 0, tonumber(inputsRight[3].Text) or 0)
	local end2   = Vector3.new(tonumber(inputsRight[4].Text) or start2.X, tonumber(inputsRight[5].Text) or start2.Y, tonumber(inputsRight[6].Text) or start2.Z)

	refreshESPFromInputs()
	executeBuild(start1, end1, start2, end2, function(success, msg)
		updateButton.Text = success and "Done!" or (msg or "Error!")
		task.delay(1, function() updateButton.Text = "Update" end)
	end)
end)

baseplateButton.MouseButton1Click:Connect(function()
	updateSettingsFromUI()
	local hrp, humanoid = getHrpAndHumanoid(baseplateButton, "Baseplate")
	if not hrp then return end

	local rows, cols = 1, 1
	local rStr, cStr = string.match(settingsData.baseSize or "", "(%d+)[xX](%d+)")
	if rStr and cStr then
		rows, cols = math.max(1, tonumber(rStr) or 1), math.max(1, tonumber(cStr) or 1)
	else
		local singleNum = string.match(settingsData.baseSize or "", "%d+")
		if singleNum then rows = math.max(1, tonumber(singleNum) or 1); cols = rows end
	end

	baseplateButton.Text = "Calculating..."

	local footPos = hrp.Position - Vector3.new(0, (hrp.Size.Y / 2) + humanoid.HipHeight + settingsData.baseOffset, 0)
	local curX, curY, curZ = math.floor((footPos.X / 2) + 0.5) * 2, math.floor((footPos.Y / 2) + 0.5) * 2, math.floor((footPos.Z / 2) + 0.5) * 2
	local totalWidthX, totalWidthZ = rows * 6, cols * 6
	local xCount, zCount = math.ceil(totalWidthX / 2), math.ceil(totalWidthZ / 2)

	local flatLook, rightVector = getCharOrientation(hrp)
	local baseCenter = Vector3.new(curX, curY, curZ)
	local halfWidthX, halfWidthZ = totalWidthX * 0.5, totalWidthZ * 0.5

	task.spawn(function()
		local function buildLayer(layerNum, isLayer2)
			baseplateButton.Text = string.format("Layer %d (%dx%d)...", layerNum, rows, cols)
			local count = isLayer2 and xCount or zCount

			for i = 0, count - 1 do
				local l1 = (isLayer2 and -halfWidthX or -halfWidthZ) + (i * 2)
				local l2 = math.min(l1 + 2, isLayer2 and halfWidthX or halfWidthZ)
				if l1 > (isLayer2 and halfWidthX or halfWidthZ) - 2 then l1 = (isLayer2 and halfWidthX or halfWidthZ) - 2 end

				local dir1 = isLayer2 and rightVector or flatLook
				local dir2 = isLayer2 and flatLook or rightVector
				local span = isLayer2 and halfWidthZ or halfWidthX

				local c1, c2 = baseCenter + dir1 * l1, baseCenter + dir1 * l2
				local s1, e1 = c1 - dir2 * span, c1 + dir2 * span
				local s2, e2 = c2 - dir2 * span, c2 + dir2 * span

				setVectorToInputs(inputsLeft, s1, 1);  setVectorToInputs(inputsLeft, e1, 4)
				setVectorToInputs(inputsRight, s2, 1); setVectorToInputs(inputsRight, e2, 4)

				local completedEvent = Instance.new("BindableEvent")
				executeBuild(s1, e1, s2, e2, function() completedEvent:Fire() end)
				completedEvent.Event:Wait()
				completedEvent:Destroy()
				task.wait(0.02)
			end
		end

		buildLayer(1, false)
		if settingsData.baseLayers >= 2 then buildLayer(2, true) end

		baseplateButton.Text = "Completed!"
		task.delay(1, function() baseplateButton.Text = "Baseplate" end)
	end)
end)

pillarButton.MouseButton1Click:Connect(function()
	updateSettingsFromUI()
	local hrp, humanoid = getHrpAndHumanoid(pillarButton, "Build Pillar")
	if not hrp then return end

	pillarButton.Text = "Building..."
	local footPos = roundVec2(hrp.Position - Vector3.new(0, (hrp.Size.Y / 2) + humanoid.HipHeight, 0))
	local _, rightVector = getCharOrientation(hrp)

	local centerPos = Vector3.new(round2(footPos.X), round2(footPos.Y), round2(footPos.Z))
	local pos1, pos2 = centerPos - rightVector, centerPos + rightVector

	local start1 = Vector3.new(round2(pos1.X), round2(footPos.Y), round2(pos1.Z))
	local end1   = Vector3.new(round2(pos1.X), round2(footPos.Y + settingsData.pillarHeight), round2(pos1.Z))
	local start2 = Vector3.new(round2(pos2.X), round2(footPos.Y), round2(pos2.Z))
	local end2   = Vector3.new(round2(pos2.X), round2(footPos.Y + settingsData.pillarHeight), round2(pos2.Z))

	setVectorToInputs(inputsLeft, start1, 1);  setVectorToInputs(inputsLeft, end1, 4)
	setVectorToInputs(inputsRight, start2, 1); setVectorToInputs(inputsRight, end2, 4)

	executeBuild(start1, end1, start2, end2, function(success, msg)
		pillarButton.Text = success and "Completed!" or (msg or "Error!")
		task.delay(1, function() pillarButton.Text = "Build Pillar" end)
	end)
end)

wallButton.MouseButton1Click:Connect(function()
	updateSettingsFromUI()
	local hrp, humanoid = getHrpAndHumanoid(wallButton, "Build Wall")
	if not hrp then return end

	wallButton.Text = "Building..."
	local footPos = roundVec2(hrp.Position - Vector3.new(0, (hrp.Size.Y / 2) + humanoid.HipHeight, 0))
	local wallHeightOffset, halfLength = settingsData.wallHeight * 0.5, settingsData.wallLength * 0.5
	local _, rightVector = getCharOrientation(hrp)
	local lengthVec = rightVector * halfLength

	local pos1, pos2 = footPos - lengthVec, footPos + lengthVec
	local start1 = Vector3.new(round2(pos1.X), round2(footPos.Y), round2(pos1.Z))
	local end1   = Vector3.new(round2(pos1.X), round2(footPos.Y + wallHeightOffset), round2(pos1.Z))
	local start2 = Vector3.new(round2(pos2.X), round2(footPos.Y), round2(pos2.Z))
	local end2   = Vector3.new(round2(pos2.X), round2(footPos.Y + wallHeightOffset), round2(pos2.Z))

	setVectorToInputs(inputsLeft, start1, 1);  setVectorToInputs(inputsLeft, end1, 4)
	setVectorToInputs(inputsRight, start2, 1); setVectorToInputs(inputsRight, end2, 4)

	executeBuild(start1, end1, start2, end2, function(success, msg)
		wallButton.Text = success and "Completed!" or (msg or "Error!")
		task.delay(1, function() wallButton.Text = "Build Wall" end)
	end)
end)

-- ================= Logic Quy Trình Lắng Nghe (Action State Machine) =================
local function resetActionButton()
	listenState = 0
	setActionSingle("Record Size", COLOR.stepBg, COLOR.text)
	tempStart1, tempStart2, tempEnd1, tempEnd2 = nil, nil, nil, nil
end

actionUndoButton.MouseButton1Click:Connect(function()
	if listenState == 2 then
		resetActionButton()
	elseif listenState == 4 then
		tempEnd1, tempEnd2 = nil, nil
		listenState = 2
		setActionSingle("End Position", COLOR.surface2, COLOR.text)
	end
end)

actionContinueButton.MouseButton1Click:Connect(function()
	if listenState == 2 then
		listenState = 3
		setActionSingle("Listening End...", COLOR.listening, COLOR.text)
	elseif listenState == 4 then
		setActionSingle("Create", COLOR.accent, COLOR.ink)
		listenState = 5
	end
end)

actionSingleButton.MouseButton1Click:Connect(function()
	if listenState == 0 then
		listenState = 1
		setActionSingle("Listening Start...", COLOR.listening, COLOR.text)
	elseif listenState == 2 or listenState == 3 then
		listenState = 3
		setActionSingle("Listening End...", COLOR.listening, COLOR.text)
	elseif listenState == 5 then
		if not tempStart1 or not tempEnd1 or not tempStart2 or not tempEnd2 then
			setActionSingle("Missing Pos!", COLOR.stepBg, COLOR.text)
			task.delay(1, function() if listenState == 5 then setActionSingle("Create", COLOR.accent, COLOR.ink) end end)
			return
		end

		setActionSingle("Building...", COLOR.accent, COLOR.ink)
		executeBuild(tempStart1, tempEnd1, tempStart2, tempEnd2, function(success, msg)
			setActionSingle(success and "Completed!" or (msg or "Error!"), COLOR.accent, COLOR.ink)
			task.wait(success and 0.8 or 1)
			resetActionButton()
		end)
	end
end)

-- ================= Menu Handle Toggle & Drag =================
local handle = create("TextButton", { Name = "Handle", Size = UDim2.fromOffset(50, 32), Position = UDim2.new(1, -70, 1, -50), BackgroundColor3 = COLOR.ink, BorderSizePixel = 0, AutoButtonColor = false, Text = "", ZIndex = 20, Parent = screenGui })
create("UICorner", { CornerRadius = UDim.new(0, 8), Parent = handle })

local handleStroke = create("UIStroke", { Name = "HandleBorder", ApplyStrokeMode = Enum.ApplyStrokeMode.Border, Color = THEME.border, Thickness = 2, Transparency = 0, Parent = handle })

for _, btn in ipairs({handle, actionSingleButton, actionUndoButton, actionContinueButton}) do addPress(btn) end

local dotsHolder = create("Frame", { BackgroundTransparency = 1, Size = UDim2.fromOffset(16, 10), Position = UDim2.new(0.5, -8, 0.5, -5), ZIndex = 21, Parent = handle })
create("UIGridLayout", { CellSize = UDim2.fromOffset(3, 3), CellPadding = UDim2.fromOffset(3, 3), FillDirectionMaxCells = 3, Parent = dotsHolder })

local dots = {}
for i = 1, 6 do
	local dot = create("Frame", { Size = UDim2.fromOffset(3, 3), BackgroundColor3 = COLOR.textMuted, BorderSizePixel = 0, ZIndex = 21, Parent = dotsHolder })
	create("UICorner", { CornerRadius = UDim.new(1, 0), Parent = dot })
	dots[i] = dot
end

local panelOpen = false
local function setOpen(open)
	panelOpen = open
	panel.Visible = open
	tw(handleStroke, TWEEN_FAST, { Color = open and THEME.accentA or THEME.border })
	for _, dot in ipairs(dots) do
		tw(dot, TWEEN_FAST, { BackgroundColor3 = open and THEME.accentA or COLOR.textMuted })
	end
end

local dragging, moved, dragInput, dragStart, startPos = false, false
local DRAG_THRESHOLD = 5

handle.InputBegan:Connect(function(input)
	if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
		dragging, moved, dragStart, startPos = true, false, input.Position, handle.Position
		input.Changed:Connect(function()
			if input.UserInputState == Enum.UserInputState.End then
				dragging = false
				if not moved then setOpen(not panelOpen) end
			end
		end)
	end
end)

handle.InputChanged:Connect(function(input)
	if input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch then
		dragInput = input
	end
end)

UserInputService.InputChanged:Connect(function(input)
	if dragging and input == dragInput then
		local delta = input.Position - dragStart
		if not moved and delta.Magnitude > DRAG_THRESHOLD then moved = true end
		if moved then
			handle.Position = UDim2.new(startPos.X.Scale, startPos.X.Offset + delta.X, startPos.Y.Scale, startPos.Y.Offset + delta.Y)
		end
	end
end)

Features & Information

Help you

Community Comments (0)

No comments yet for this script.

More Scripts for GEF