I need help with the train base script


I have created a script for moving parts, where the parts follow each other, and the first part follows the route, but the more parts I create, the more the route gets disrupted, which looks very bad.
Please help me fix this issue. I understand that I need to make some kind of offset, but I have not been able to achieve it.
Here is my current script:

local RunService = game:GetService('RunService')

local RailFolder = workspace:WaitForChild('Rail')

local IncludeTable = {}
for i, z in pairs(RailFolder:GetDescendants()) do
	if z.Name == 'Ballast' then
		table.insert(IncludeTable, z)
	end
end
local RayCastParams = RaycastParams.new()
RayCastParams.FilterDescendantsInstances = IncludeTable
RayCastParams.FilterType = Enum.RaycastFilterType.Include
RayCastParams.IgnoreWater = true
local distance = 100

local Spd = 100
local Revers = 'F'
local Height = 7
local CurrentSeat = nil
local LastSeat = nil
local CurrentSeatPer
local ForwardRayPart = nil
local BackwardRayPart = nil

local MovementStarted = false
local RunSeviceConnect

local OptionsDistance = {}
local EndBaseParts = {}
local ForwardRayBaseTable = {}
local BackRayBaseTable = {}
local Seats = {}
local AllBaseparts = {}
local Parent = script.Parent
	
local function StartMove (Val)
	if not MovementStarted then
		local LerpTable = {{true}}
		MovementStarted = true
		
		local function findrail(Raypart)
			local RayOrigin = Raypart.CFrame.Position
			local RayDist = Raypart.CFrame.LookVector * distance
			local RayRes = workspace:Raycast(RayOrigin, RayDist, RayCastParams)
			if RayRes then
				distance = RayRes.Distance + RayRes.Distance
				return RayRes
			end
		end
		
		local function OffsetPoint (FirstPart, SecondPart)
			
		end
		
		--local function MinDistantPoint (FirstPart, SecondPart)
		--	local FirstCFrameLook = FirstPart.CFrame.LookVector
			
		--	local firstPos = FirstPart.CFrame.Position + (FirstCFrameLook * (FirstPart.Size.Z / 2)) 
		--	local TwoPos = FirstPart.CFrame.Position - (FirstCFrameLook * (FirstPart.Size.Z / 2))
		--	local Pos = SecondPart.CFrame.Position
			
		--	local OneMagn = (firstPos - Pos).Magnitude
		--	local TwoMagn = (TwoPos- Pos).Magnitude
		--	local min = math.min(OneMagn, TwoMagn)
		--	local UsePos = FirstPart.CFrame
		--	--print(FirstPart)
		--	if min == OneMagn then
		--		UsePos += (FirstCFrameLook * (FirstPart.Size.Z / 2))
		--	elseif min == TwoMagn then
		--		UsePos -= (FirstCFrameLook * (FirstPart.Size.Z / 2))
		--	end
		--	return UsePos
		--end
		
		local function MaxDistantPoint (Ballast, CurrentBasePart, RayPart)
			
			local PositionBase = CurrentBasePart.CFrame.Position
			local railCframelook = Ballast.CFrame.LookVector
			
			local firstPos = Ballast.CFrame.Position + (railCframelook * (Ballast.Size.Z / 2)) 
			local TwoPos = Ballast.CFrame.Position - (railCframelook * (Ballast.Size.Z / 2))
			local RayLookPos = RayPart.CFrame.Position + (RayPart.CFrame.LookVector * 10000)
			local OneMagn = math.round((RayLookPos - firstPos).Magnitude)
			local TwoMagn = math.round((RayLookPos - TwoPos).Magnitude)
			local min = math.min(OneMagn, TwoMagn)
			local CurrentUsePos = Ballast.CFrame + (Ballast.CFrame.UpVector * Height)
			if min == OneMagn then
				local Magnitude = (PositionBase - (CurrentUsePos.Position + (railCframelook * Ballast.Size / 2))).Magnitude
				if Magnitude - 1 < Ballast.Size.Z / 2 then
					CurrentUsePos += railCframelook * Ballast.Size.Z / 2
				end
			else
				local Magnitude = (PositionBase - (CurrentUsePos.Position - (railCframelook * Ballast.Size / 2))).Magnitude
				if Magnitude - 1 < Ballast.Size.Z / 2 then
					CurrentUsePos -= railCframelook * Ballast.Size.Z / 2
				end
			end
			
			return CurrentUsePos
		end
		
		local function ChangeRotFunct (Part, SecondPart)
			local railCframelook = Part.CFrame.LookVector
			local basepartCframelook = SecondPart.CFrame.LookVector
			local ChangeRot = false
			local OneX = math.round(railCframelook.Unit.X)
			local TwoX = math.round(basepartCframelook.Unit.X)
			local OneZ = math.round(railCframelook.Unit.Z)
			local TwoZ = math.round(basepartCframelook.Unit.Z)
			local kof = 1
			if OneX ~= 0 and TwoX ~= 0 then
				if OneX + TwoX == 0 then
					ChangeRot = true
				else
					ChangeRot = false
				end
			else
				if TwoZ + OneZ == 0 then
					ChangeRot = true
				else
					ChangeRot = false
				end
			end
			return ChangeRot
		end
		
		RunSeviceConnect = RunService.Heartbeat:Connect(function(deltatime)
			local LocalPerSeat = 1
			if Revers == 'F' then
				if CurrentSeatPer == -1 then
					LocalPerSeat = CurrentSeatPer * -1
				else
					LocalPerSeat = CurrentSeatPer * 1
				end
			elseif Revers == 'B' then
				if CurrentSeatPer == -1 then
					LocalPerSeat = CurrentSeatPer * 1
				else
					LocalPerSeat = CurrentSeatPer * -1
				end
			end
			local CurrentUseRay = ForwardRayPart
			local CurrentUseBaseTable = ForwardRayBaseTable
			if LocalPerSeat == -1 then
				CurrentUseRay = BackwardRayPart
				CurrentUseBaseTable = BackRayBaseTable
			end
			local LeaderBasePart = CurrentUseBaseTable[1][1]
			if LerpTable[1][1] then
				local Rail = findrail(CurrentUseRay)
				if Rail then
					local Ballast = Rail.Instance
					local MovePos = MaxDistantPoint(Ballast, LeaderBasePart, CurrentUseRay)
					local ChangeRot = ChangeRotFunct(Ballast, LeaderBasePart)
					if ChangeRot then
						MovePos *= CFrame.Angles(0, math.rad(180), 0)
					end
					if LerpTable[1][2] ~= nil then
						local Tab = LerpTable[1]
						Tab[1] = false
						Tab[2] = LeaderBasePart
						Tab[3] = 0
						Tab[4] = LeaderBasePart.CFrame
						Tab[5] = MovePos
					else
						local Tab = LerpTable[1]
						Tab[1] = false
						table.insert(Tab, LeaderBasePart)
						table.insert(Tab, 0)
						table.insert(Tab, LeaderBasePart.CFrame)
						table.insert(Tab, MovePos)
					end
				end
			end
			for i = 2, #CurrentUseBaseTable, 1 do
				local Tab = CurrentUseBaseTable[i]
				local FollowTab = CurrentUseBaseTable[i - 1]
				local BasePart = Tab[1]
				local FollowPart = FollowTab[1]
				local Distance = Tab[2]
				if LerpTable[i] == nil or LerpTable[i][4] then
					local LeaderCFrame = FollowPart.CFrame
					local ChangeRot = ChangeRotFunct(BasePart, FollowPart)
					if ChangeRot then
						LeaderCFrame *= CFrame.Angles(0, math.rad(180), 0)
					end
					--local ChangeOr = BasePart.CFrame.Rotation * LeaderCFrame.Rotation:Inverse()
					--local LeaderCFrame = LeaderCFrame * ChangeOr:
					if LerpTable[i] == nil then
						table.insert(LerpTable, {0, BasePart.CFrame, LeaderCFrame, false})
					else
						local Tab = LerpTable[i]
						Tab[1] = 0
						Tab[2] = BasePart.CFrame
						Tab[3] = LeaderCFrame
						Tab[4] = false
					end
				end
				local Tab = LerpTable[i]
				local Alpha = Tab[1]
				local StartPose = Tab[2]
				local EndPose = Tab[3]
				local Lenght = (EndPose.Position - StartPose.Position).Magnitude
				local LocalSpd = Spd
				local AddLenght = (BasePart.CFrame.Position - FollowPart.CFrame.Position).Magnitude
				local Var = Distance - AddLenght
				LocalSpd -= Var * #CurrentUseBaseTable
				local AddSpd = LocalSpd
				AddSpd /= Lenght
				AddSpd *= deltatime
				Alpha += AddSpd
				local MaxAlpha = 1
				Alpha = math.clamp(Alpha, 0, MaxAlpha)
				if Alpha >= MaxAlpha then
					if not Tab[4] then
						Tab[4] = true
					end
				end
				Tab[1] = Alpha
				local Goal = StartPose:Lerp(EndPose, Alpha)
				BasePart.CFrame = Goal
			end
			if LerpTable[1] ~= nil then
				local Tab = LerpTable[1]
				local Alpha = LerpTable[1][3]
				local BasePartS = LerpTable[1][2]
				local StartPose = LerpTable[1][4]
				local EndPose = LerpTable[1][5]
				local Needed = LerpTable[1][1]
				--print(deltatime)
				local Lenght = (EndPose.Position - StartPose.Position).Magnitude
				local AddSpd = Spd
				AddSpd /= Lenght
				AddSpd *= deltatime
				Alpha += AddSpd 
				Alpha = math.clamp(Alpha, 0, 1)
				--print(Alpha)
				Tab[3] = Alpha
				if Alpha >= 0.7 then
					if not Tab[1] then
						Tab[1] = true
					end
				end
				local Goal = StartPose:Lerp(EndPose, Alpha)
				BasePartS.CFrame = Goal
			end
		end)
	else
		if Val == 'Stop' then
			RunSeviceConnect:Disconnect()
			MovementStarted = false
		end
	end
end

for i, a in pairs(Parent:GetDescendants()) do
	if a.Name == 'BasePart' then
		table.insert(AllBaseparts, a)
	elseif a:IsA('Seat') or a:IsA('VehicleSeat') then
		table.insert(Seats, a)
	end
end

local function OptD (CurrentBase)
	local CFrameBase = CFrame.new(CurrentBase.Position)
	local Pos = CurrentBase.Position - CFrameBase.LookVector * 10
	--local Pos = CurrentBase.Position
	local newtab = {}
	for i, k in AllBaseparts do
		if k ~= CurrentBase then
			local magnitude = (k.Position - Pos).Magnitude
			table.insert(newtab, magnitude)
		end
	end
	table.sort(newtab)
	local first = newtab[1]
	local two = newtab[2]
	local fpart
	local tpart
	for i, a in AllBaseparts do
		if a ~= CurrentBase then
			local magnitude = (a.Position - Pos).Magnitude
			if first and magnitude == first and not fpart then
				fpart = a
			end
			if two and two < first * 1.5 and magnitude == two and not tpart then
				tpart = a
			end
		end
	end
	local Pos2 = CurrentBase.Position
	if fpart then
		first = (fpart.Position - Pos2).Magnitude
	end
	if tpart then
		two = (tpart.Position - Pos2).Magnitude
	end
	if fpart and tpart then
		table.insert(OptionsDistance, {CurrentBase, fpart, tpart, first, two})
	elseif fpart then
		table.insert(EndBaseParts, CurrentBase)
		table.insert(OptionsDistance, {CurrentBase, fpart, 'none', first, 'none'})
	end
end
for i, l in pairs(AllBaseparts) do
	OptD(l)
end

local function GetRay ()
	local PositionMove = CurrentSeat.Position + CurrentSeat.CFrame.LookVector * 5000
	
	local function CheckDistance (FirstPart, SecondPart)
		local Base = FirstPart.Position
		local Base2 = SecondPart.Position
		local FirstMagnitude = (PositionMove - Base).Magnitude
		local SecondMagnitude = (PositionMove - Base2).Magnitude
		local Min = math.min(FirstMagnitude, SecondMagnitude)
		if Min == FirstMagnitude then
			return FirstPart, SecondPart
		else
			return SecondPart, FirstPart
		end
	end
	
	local function AddToTable (RecBasePart, AddTab)
		local ExcludeFirst = RecBasePart
		local ExcludeSecond = nil
		local noneNum = 0
		repeat
			for i, a in pairs(OptionsDistance) do
				--print(a)
				local First = a[1]
				local Second = a[2]
				local Third = a[3]
				local SecondDist = a[4]
				local ThirdDist = a[5]
				if First == ExcludeFirst then
					local Tab = {}
					table.insert(Tab, First)
					if Third == 'none' then
						noneNum += 1
						ExcludeFirst = Second
						table.insert(Tab, SecondDist)
					elseif ExcludeSecond == Second then
						ExcludeFirst = Third
						table.insert(Tab, ThirdDist)
					elseif ExcludeSecond == Third then
						ExcludeFirst = Second
						table.insert(Tab, SecondDist)
					end
					table.insert(AddTab, Tab)
					ExcludeSecond = First
					break
				end
			end
			task.wait()
		until noneNum > 1
	end
	table.clear(ForwardRayBaseTable)
	table.clear(BackRayBaseTable)
	if #EndBaseParts == 2 then
		local ForwardBase, Backward = CheckDistance(EndBaseParts[1], EndBaseParts[2])
		local ForwardBaseRays = {}
		for i, m in ForwardBase.Parent:GetChildren() do
			local name = string.lower(m.Name)
			if string.find(name, 'ray') then
				table.insert(ForwardBaseRays, m)
			end
		end
		AddToTable(ForwardBase, ForwardRayBaseTable)
		AddToTable(Backward, BackRayBaseTable)
		local ForwardRay, BackRay = CheckDistance(ForwardBaseRays[1], ForwardBaseRays[2])
		ForwardRayPart = ForwardRay
		local BackwardBaseRays = {}
		for i, z in pairs(Backward.Parent:GetChildren()) do
			local name = string.lower(z.Name)
			if string.find(name, 'ray') then
				table.insert(BackwardBaseRays, z)
			end
		end
		local ForwardBRay, BackBRay = CheckDistance(BackwardBaseRays[1], BackwardBaseRays[2])
		BackwardRayPart = BackBRay
	else
		local RaysTab = {}
		for i, a in pairs((EndBaseParts[1].Parent:GetChildren())) do
			local name = string.lower(a.Name)
			if string.find(name, 'ray') then
				table.insert(RaysTab, a)
			end
		end
		table.insert(ForwardRayBaseTable, EndBaseParts[1])
		table.insert(BackRayBaseTable, EndBaseParts[2])
		local ForwardRay, BackRay = CheckDistance(RaysTab[1], RaysTab[2])
		ForwardRayPart = ForwardRay
		BackwardRayPart = BackRay
	end
	StartMove()
end

for i, m in pairs(Seats) do
	m:GetPropertyChangedSignal('Occupant'):Connect(function()
		if m.Occupant then
			CurrentSeat = m
			if m:GetAttribute('Front') then
				CurrentSeatPer = 1
			else
				CurrentSeatPer = -1
			end
			GetRay()
		else
			--StartMove('Stop')
		end
	end)
end

--print(OptionsDistance)
--print(EndBaseParts)



1 Like