local s={}local P={}local function m(s,P,T,w,r)if r==nil then r=0 end r=r+1 local v=Ray.new(s,P*T)local G,X=Workspace:FindPartOnRayWithIgnoreList(v,w)if G then if G.CanCollide==false and r<=5 then local v=T-(s-X).magnitude G,X=m(X,P,v,w,r)end end return G,X end local function T(P,T,w,r)local v=T:FindFirstChild("\072\117\109\097\110\111\105\100\082\111\111\116\080\097\114\116")if v then r:Destroy()if w:FindFirstChild("\068\111\111\114\072\105\110\103\101")then w.DoorLatchWeld.Enabled=false w.DoorHinge.TargetAngle=DOOR_OPEN_ANGLE end s[w]=s[w]and s[w]+1 or 1 wait()if w:FindFirstChild("\069\120\105\116\080\111\115\105\116\105\111\110")then local s local P=w repeat s=P P=s.Parent until P.ClassName~="\077\111\100\101\108"local r=w.ExitPosition.WorldPosition local G=r-w.Position local X=G.magnitude local L=G.unit local t,u=m(w.Position,L,X,{T,s})if not t then v.CFrame=CFrame.new(r)else v.CFrame=CFrame.new(w.Position)T:MoveTo(w.Position+Vector3.new(0,8,0))end else v.CFrame=CFrame.new(w.Position)T:MoveTo(w.Position+Vector3.new(0,8,0))end RemotesFolder.ExitSeat:FireClient(P,true)wait(DOOR_OPEN_TIME)s[w]=s[w]>1 and s[w]-1 or nil if w:FindFirstChild("\068\111\111\114\072\105\110\103\101")then if s[w]==nil then w.DoorHinge.TargetAngle=0 while math.abs(w.DoorHinge.CurrentAngle)>.01 do wait()end w.DoorLatchWeld.Enabled=true end end end end local function w(s,P)if P then if P.Parent then if not P.Parent.Parent:FindFirstChild("\083\099\114\105\112\116\115")then warn("\070\108\105\112 \069\114\114\111\114\058 \083\099\114\105\112\116\115 \102\105\108\101 \110\111\116 \102\111\117\110\100\046 \080\108\101\097\115\101 \112\097\114\101\110\116 \115\101\097\116\115 \116\111 \116\104\101 \099\104\097\115\115\105\115 \109\111\100\101\108")return end if not P.Parent.Parent.Scripts:FindFirstChild("\067\104\097\115\115\105\115")then warn("\070\108\105\112 \069\114\114\111\114\058 \067\104\097\115\115\105\115 \109\111\100\117\108\101 \110\111\116 \102\111\117\110\100\046")return end local s=require(P.Parent.Parent.Scripts.Chassis)s.Redress()end end end function P.SetRemotesFolder(P)RemotesFolder=P;(RemotesFolder:FindFirstChild("\071\101\116\083\101\097\116\084\097\103")).OnServerInvoke=function(s)return SeatTag end;(RemotesFolder:FindFirstChild("\069\120\105\116\083\101\097\116")).OnServerEvent:Connect(function(s)if s.Character then local P=s.Character if s.Character:FindFirstChild("\072\117\109\097\110\111\105\100\082\111\111\116\080\097\114\116")then for m,w in pairs(P.HumanoidRootPart:GetJoints())do if w.Name=="\083\101\097\116\087\101\108\100"then T(s,P,w.Part0,w)break end end end end end);(RemotesFolder:FindFirstChild("\070\108\105\112\083\101\097\116")).OnServerEvent:Connect(w);(RemotesFolder:FindFirstChild("\071\101\116\073\110\083\116\097\114\116")).OnServerEvent:Connect(function(P,m)if m:FindFirstChild("\068\111\111\114\072\105\110\103\101")then if m.DoorHinge.ClassName~="\072\105\110\103\101\067\111\110\115\116\114\097\105\110\116"then warn("\087\097\114\110\105\110\103\044 \100\111\111\114 \104\105\110\103\101 \105\115 \110\111\116 \097\099\116\117\097\108\108\121 \097 \104\105\110\103\101\033")end s[m]=s[m]and s[m]+1 or 1 m.DoorLatchWeld.Enabled=false m.DoorHinge.TargetAngle=DOOR_OPEN_ANGLE m.DoorHinge.AngularSpeed=DOOR_OPEN_SPEED wait(DOOR_OPEN_TIME)s[m]=s[m]>1 and s[m]-1 or nil if s[m]==nil then m.DoorHinge.TargetAngle=0 while math.abs(m.DoorHinge.CurrentAngle)>.01 do wait()end m.DoorLatchWeld.Enabled=true end end end)RemotesFolder.GetInSeat.OnServerEvent:Connect(function(s,P)if P then if not P:FindFirstChild("\083\101\097\116\087\101\108\100")then if s.Character~=nil then local m=s.Character:FindFirstChild("\072\117\109\097\110\111\105\100\082\111\111\116\080\097\114\116")local T=s.Character:FindFirstChild("\072\117\109\097\110\111\105\100")if m then local s=(m.Position-P.Position).magnitude if s<=MAX_SEATING_DISTANCE then P:Sit(T)end end end end end end)end function P.AddSeat(s,P,m)CollectionService:AddTag(s,SeatTag)local T=nil local w=(s:GetPropertyChangedSignal("\079\099\099\117\112\097\110\116")):Connect(function()if s.Occupant then local m=s.Occupant.Parent T=Players:GetPlayerFromCharacter(m)if T and P then P(T,s)end elseif T and m then m(T,s)T=nil end end)s.AncestryChanged:connect(function()if not s:IsDescendantOf(game)then w:Disconnect()end end)end return P
--Variables
local SeatInteractionCount = {}

local VehicleSeating = {}

local function Raycast(startPos, direction, range, ignore, inceptNumber)
	if inceptNumber == nil then
		inceptNumber = 0
	end
	inceptNumber = inceptNumber + 1
	local ray = Ray.new(startPos, direction * range)
	local part, position = Workspace:FindPartOnRayWithIgnoreList(ray, ignore)
	if part then
		if part.CanCollide == false and inceptNumber <= 5 then
			--raycast again if we hit a cancollide false brick, put a limit on to prevent an infinite loop
			local rangeLeft = range - (startPos - position).magnitude
			part, position = Raycast(position, direction, rangeLeft, ignore, inceptNumber) --Raycast remaining distance.
		end
	end
	return part, position
end

local function ExitSeat(player, character, seat, weld)
	local hrp = character:FindFirstChild("HumanoidRootPart")
	if hrp then
		weld:Destroy()

		if seat:FindFirstChild("DoorHinge") then
			seat.DoorLatchWeld.Enabled = false
			seat.DoorHinge.TargetAngle = DOOR_OPEN_ANGLE
		end
		--Record the interaction
		SeatInteractionCount[seat] = SeatInteractionCount[seat] and SeatInteractionCount[seat] + 1 or 1

		wait()
		if seat:FindFirstChild("ExitPosition") then --Check if we can move the character to the designated pos.
			--Find vehicle model
			local model
			local newParent = seat
			repeat
				model = newParent
				newParent = model.Parent
			until newParent.ClassName ~= "Model"

			local targetPos = seat.ExitPosition.WorldPosition
			local delta = targetPos - seat.Position
			local dist = delta.magnitude
			local dir = delta.unit
			local part, _ = Raycast(seat.Position, dir, dist, { character, model })

			if not part then --Prevent people being CFramed into walls and stuff
				hrp.CFrame = CFrame.new(targetPos)
			else
				hrp.CFrame = CFrame.new(seat.Position)
				--The CFrame element orients the character up-right, the MoveTo stops the character from clipping into objects
				character:MoveTo(seat.Position + Vector3.new(0, 8, 0))
			end
		else
			hrp.CFrame = CFrame.new(seat.Position)
			character:MoveTo(seat.Position + Vector3.new(0, 8, 0))
		end

		RemotesFolder.ExitSeat:FireClient(player, true) --Fire this to trigger the client-side anti-trip function

		wait(DOOR_OPEN_TIME)

		SeatInteractionCount[seat] = SeatInteractionCount[seat] > 1 and SeatInteractionCount[seat] - 1 or nil
		if seat:FindFirstChild("DoorHinge") then
			--If nobody else has interactied in this time, close the door.
			if SeatInteractionCount[seat] == nil then
				seat.DoorHinge.TargetAngle = 0
				-- Weld door shut when closed
				while math.abs(seat.DoorHinge.CurrentAngle) > 0.01 do
					wait()
				end
				seat.DoorLatchWeld.Enabled = true
			end
		end
	end
end

local function FlipSeat(Player, Seat)
	if Seat then
		if Seat.Parent then
			if not Seat.Parent.Parent:FindFirstChild("Scripts") then
				warn("Flip Error: Scripts file not found. Please parent seats to the chassis model")
				return
			end
			if not Seat.Parent.Parent.Scripts:FindFirstChild("Chassis") then
				warn("Flip Error: Chassis module not found.")
				return
			end
			local Chassis = require(Seat.Parent.Parent.Scripts.Chassis)
			Chassis.Redress()
		end
	end
end

function VehicleSeating.SetRemotesFolder(remotes)
	RemotesFolder = remotes

	RemotesFolder:FindFirstChild("GetSeatTag").OnServerInvoke = function(player)
		return SeatTag
	end

	--Detect exit seat requests
	RemotesFolder:FindFirstChild("ExitSeat").OnServerEvent:Connect(function(player)
		if player.Character then
			local character = player.Character
			if player.Character:FindFirstChild("HumanoidRootPart") then
				for _, weld in pairs(character.HumanoidRootPart:GetJoints()) do
					if weld.Name == "SeatWeld" then
						ExitSeat(player, character, weld.Part0, weld)
						break
					end
				end
			end
		end
	end)

	RemotesFolder:FindFirstChild("FlipSeat").OnServerEvent:Connect(FlipSeat)

	--When a player holds down the interaction button, open the vehicle door.
	--This only serves a purpose if there are doors to open/close
	RemotesFolder:FindFirstChild("GetInStart").OnServerEvent:Connect(function(player, seat)
		if seat:FindFirstChild("DoorHinge") then
			if seat.DoorHinge.ClassName ~= "HingeConstraint" then
				warn("Warning, door hinge is not actually a hinge!")
			end

			--Record that a player is trying to get in the seat
			SeatInteractionCount[seat] = SeatInteractionCount[seat] and SeatInteractionCount[seat] + 1 or 1

			--Activate the hinge
			seat.DoorLatchWeld.Enabled = false
			seat.DoorHinge.TargetAngle = DOOR_OPEN_ANGLE
			seat.DoorHinge.AngularSpeed = DOOR_OPEN_SPEED

			wait(DOOR_OPEN_TIME)

			--Check if anyone has interacted with the door within this time. If not, close it.
			SeatInteractionCount[seat] = SeatInteractionCount[seat] > 1 and SeatInteractionCount[seat] - 1 or nil
			if SeatInteractionCount[seat] == nil then
				seat.DoorHinge.TargetAngle = 0
				-- Weld door shut when closed
				while math.abs(seat.DoorHinge.CurrentAngle) > 0.01 do
					wait()
				end
				seat.DoorLatchWeld.Enabled = true
			end
		end
	end)

	--Actually put the character in the seat when requested.
	RemotesFolder.GetInSeat.OnServerEvent:Connect(function(Player, Seat)
		if Seat then
			if not Seat:FindFirstChild("SeatWeld") then
				if Player.Character ~= nil then
					local HRP = Player.Character:FindFirstChild("HumanoidRootPart")
					local humanoid = Player.Character:FindFirstChild("Humanoid")
					if HRP then
						local Dist = (HRP.Position - Seat.Position).magnitude
						if Dist <= MAX_SEATING_DISTANCE then
							Seat:Sit(humanoid)
						end
					end
				end
			end
		end
	end)
end

function VehicleSeating.AddSeat(seat, enterCallback, exitCallback)
	CollectionService:AddTag(seat, SeatTag)

	local occupantPlayer = nil
	local occupantChangedConn = seat:GetPropertyChangedSignal("Occupant"):Connect(function()
		if seat.Occupant then
			local char = seat.Occupant.Parent
			occupantPlayer = Players:GetPlayerFromCharacter(char)
			if occupantPlayer and enterCallback then
				enterCallback(occupantPlayer, seat)
			end
		elseif occupantPlayer and exitCallback then
			exitCallback(occupantPlayer, seat)
			occupantPlayer = nil
		end
	end)

	--Clean up after the seat is destroyed
	seat.AncestryChanged:connect(function()
		if not seat:IsDescendantOf(game) then
			occupantChangedConn:Disconnect()
		end
	end)
end

return VehicleSeating
