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

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
