--!strict local Players = game:GetService("Players") local RunService = game:GetService("RunService") local player = Players.LocalPlayer -- ========================= -- CONFIG -- ========================= local CONFIG = { TargetSpeed = 250, -- ความเร็วหลักแกน Z RoadName = "AutoFarmRoad", RoadSize = Vector3.new(200, 10, 900000), RoadPosition = Vector3.new(0, 10000, 0), SpawnPosition = Vector3.new(0, 10015, 0), SpawnYaw = 0, -- องศาที่หันตอนวางรถกลับ MinYBeforeReset = 9500, MaxZBeforeReset = 400000, CenterX = 0, -- กลางถนน CenteringGain = 4.5, -- แรงดึงกลับแกน X MaxSideVelocity = 120, -- จำกัดความเร็วด้านข้าง ไม่ให้ดีดแรงเกิน TeleportCooldown = 0.25, OrientationResponsiveness = 50, -- ค่ายิ่งมากยิ่งหันไว OrientationMaxTorque = 1e9, } -- ========================= -- STATE -- ========================= local currentVehicle: Model? = nil local currentMainPart: BasePart? = nil local isResetting = false local heartbeatConnection: RBXScriptConnection? = nil -- ========================= -- ROAD SETUP -- ========================= local function ensureRoad(): Part local road = workspace:FindFirstChild(CONFIG.RoadName) if road and road:IsA("Part") then road.Size = CONFIG.RoadSize road.Position = CONFIG.RoadPosition return road end if road and not road:IsA("Part") then road:Destroy() end local newRoad = Instance.new("Part") newRoad.Name = CONFIG.RoadName newRoad.Size = CONFIG.RoadSize newRoad.Position = CONFIG.RoadPosition newRoad.Anchored = true newRoad.CanCollide = true newRoad.CanTouch = false newRoad.CanQuery = false newRoad.Transparency = 0.5 newRoad.Material = Enum.Material.SmoothPlastic newRoad.CastShadow = false newRoad.Locked = true newRoad.Parent = workspace return newRoad end ensureRoad() -- ========================= -- UTILS -- ========================= local function getCharacter(): Model? return player.Character end local function getHumanoid(character: Model?): Humanoid? if not character then return nil end return character:FindFirstChildOfClass("Humanoid") end local function getSeatedVehicle(): (Model?, BasePart?) local character = getCharacter() local humanoid = getHumanoid(character) if not character or not humanoid then return nil, nil end local seat = humanoid.SeatPart if not seat or not seat:IsA("BasePart") then return nil, nil end local vehicle = seat:FindFirstAncestorOfClass("Model") if not vehicle then return nil, nil end local mainPart = vehicle.PrimaryPart or seat if not mainPart or not mainPart:IsA("BasePart") then return nil, nil end return vehicle, mainPart end local function clearControllerObjects(part: BasePart?) if not part then return end local oldAttachment = part:FindFirstChild("AF_Attachment") if oldAttachment then oldAttachment:Destroy() end local oldAlign = part:FindFirstChild("AF_AlignOrientation") if oldAlign then oldAlign:Destroy() end end local function setupOrientationController(part: BasePart) clearControllerObjects(part) local attachment = Instance.new("Attachment") attachment.Name = "AF_Attachment" attachment.Parent = part local align = Instance.new("AlignOrientation") align.Name = "AF_AlignOrientation" align.Mode = Enum.OrientationAlignmentMode.OneAttachment align.Attachment0 = attachment align.RigidityEnabled = false align.ReactionTorqueEnabled = false align.Responsiveness = CONFIG.OrientationResponsiveness align.MaxTorque = CONFIG.OrientationMaxTorque align.CFrame = CFrame.lookAt(Vector3.zero, Vector3.new(0, 0, 1), Vector3.new(0, 1, 0)) align.Parent = part end local function bindVehicle(vehicle: Model, mainPart: BasePart) if currentMainPart == mainPart and currentVehicle == vehicle then return end if currentMainPart then clearControllerObjects(currentMainPart) end currentVehicle = vehicle currentMainPart = mainPart setupOrientationController(mainPart) end local function unbindVehicle() if currentMainPart then clearControllerObjects(currentMainPart) end currentVehicle = nil currentMainPart = nil end local function resetVehicle(vehicle: Model?, mainPart: BasePart?) if isResetting or not mainPart then return end isResetting = true mainPart.AssemblyLinearVelocity = Vector3.zero mainPart.AssemblyAngularVelocity = Vector3.zero local spawnCF = CFrame.new(CONFIG.SpawnPosition) * CFrame.Angles(0, math.rad(CONFIG.SpawnYaw), 0) if vehicle then vehicle:PivotTo(spawnCF) else mainPart.CFrame = spawnCF end task.delay(CONFIG.TeleportCooldown, function() isResetting = false end) end -- ========================= -- MAIN LOOP -- ========================= local function stepVehicle() if isResetting then return end local vehicle, mainPart = getSeatedVehicle() if not vehicle or not mainPart then unbindVehicle() return end bindVehicle(vehicle, mainPart) local pos = mainPart.Position -- รีเซ็ตกลับต้นทางถ้าหลุดจากพื้นที่ที่กำหนด if pos.Y < CONFIG.MinYBeforeReset or pos.Z > CONFIG.MaxZBeforeReset then resetVehicle(vehicle, mainPart) return end -- ล็อกอัตราหมุนไม่ให้รถเหวี่ยง/ล้มง่าย mainPart.AssemblyAngularVelocity = Vector3.zero -- ดึงกลับสู่แกนกลางถนน local lateralError = CONFIG.CenterX - pos.X local sideVelocity = math.clamp( lateralError * CONFIG.CenteringGain, -CONFIG.MaxSideVelocity, CONFIG.MaxSideVelocity ) -- คง Y velocity เดิมไว้ เพื่อไม่ฝืนแรงโน้มถ่วงจนผิดธรรมชาติ local currentYVelocity = mainPart.AssemblyLinearVelocity.Y mainPart.AssemblyLinearVelocity = Vector3.new( sideVelocity, currentYVelocity, CONFIG.TargetSpeed ) end -- กันรันซ้ำแล้วมีหลาย connection if _G.AutoDriveConnection then _G.AutoDriveConnection:Disconnect() _G.AutoDriveConnection = nil end heartbeatConnection = RunService.Heartbeat:Connect(stepVehicle) _G.AutoDriveConnection = heartbeatConnection -- cleanup ตอนตัวละครเปลี่ยน player.CharacterRemoving:Connect(function() unbindVehicle() end)