local function k(k)local u=k.CFrame.upVector local B=math.deg(math.acos(u:Dot(Vector3.new(0,1,0))))return B>=MIN_FLIP_ANGLE end local function u(k,B,R,H,O)if O==nil then O=0 end O=O+1 local z=Ray.new(k,B*R)local t,X=Workspace:FindPartOnRayWithIgnoreList(z,H)if t then if t.CanCollide==false and O<=5 then local z=R-(k-X).magnitude t,X=u(X,B,z,H,O)end end return t,X end local function B(k,B,R,H)local O=B:FindFirstChild("\072\117\109\097\110\111\105\100\082\111\111\116\080\097\114\116")if O then H:Destroy()if R:FindFirstChild("\068\111\111\114\072\105\110\103\101")then R.DoorLatchWeld.Enabled=false R.DoorHinge.TargetAngle=DOOR_OPEN_ANGLE playDoorSound(R,"\079\112\101\110\067\108\111\115\101")end SeatInteractionCount[R]=SeatInteractionCount[R]and SeatInteractionCount[R]+1 or 1 task.wait(.1)if R:FindFirstChild("\069\120\105\116\080\111\115\105\116\105\111\110")then local k local H=R repeat k=H H=k.Parent until H.ClassName~="\077\111\100\101\108"local z=R.ExitPosition.WorldPosition local t=z-R.Position local X=t.magnitude local j=t.unit local v,S=u(R.Position,j,X,{B;k})if not v then O.CFrame=CFrame.new(z)else O.CFrame=CFrame.new(R.Position)B:MoveTo(R.Position+Vector3.new(0,8,0))end else O.CFrame=CFrame.new(R.Position)B:MoveTo(R.Position+Vector3.new(0,8,0))end if k then RemotesFolder.ExitSeat:FireClient(k,true)end task.wait(DOOR_OPEN_TIME)SeatInteractionCount[R]=SeatInteractionCount[R]>1 and SeatInteractionCount[R]-1 or nil if R:FindFirstChild("\068\111\111\114\072\105\110\103\101")then if SeatInteractionCount[R]==nil then R.DoorHinge.TargetAngle=0 while math.abs(R.DoorHinge.CurrentAngle)>.01 do task.wait()end R.DoorLatchWeld.Enabled=true end end end end local function R(k,u)if u then if u.Parent then if not u.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 u.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 k=require(u.Parent.Parent.Scripts.Chassis)k.Redress()end end end function VehicleSeating.EjectCharacter(k)if k and k.HumanoidRootPart then for u,R in pairs(k.HumanoidRootPart:GetJoints())do if R.Name=="\083\101\097\116\087\101\108\100"then B(Players:GetPlayerFromCharacter(k),k,R.Part0,R)break end end end end function VehicleSeating.SetRemotesFolder(k)RemotesFolder=k;(RemotesFolder:FindFirstChild("\069\120\105\116\083\101\097\116")).OnServerEvent:Connect(function(k)if k.Character then local u=k.Character VehicleSeating.EjectCharacter(u)end end);(RemotesFolder:FindFirstChild("\070\111\114\099\101\069\120\105\116\083\101\097\116")).OnServerEvent:Connect(function(k)local u=PackagedVehicle:FindFirstChild("\067\104\097\115\115\105\115")if u then local B=u:FindFirstChild(k)if B and B.Occupant then local k=B.Occupant.Parent VehicleSeating.EjectCharacter(k)end end end)end function VehicleSeating.SetBindableEventsFolder(k)local u=k;(u:FindFirstChild("\070\111\114\099\101\069\120\105\116\083\101\097\116")).Event:Connect(function(k)local u=PackagedVehicle:FindFirstChild("\067\104\097\115\115\105\115")if u then local B=u:FindFirstChild(k)if B and B.Occupant then local k=B.Occupant.Parent VehicleSeating.EjectCharacter(k)end end end)end function VehicleSeating.AddSeat(u,H,O)local z=u:FindFirstChild("\080\114\111\109\112\116\076\111\099\097\116\105\111\110")if z then local H=z:FindFirstChildWhichIsA("\080\114\111\120\105\109\105\116\121\080\114\111\109\112\116")if H then local O=getVehicleObject()if O:GetAttribute("\072\101\097\108\116\104")<=0 then return end local function z()if u.Occupant and not carjackingEnabled(O)then H.Enabled=false else H.Enabled=true end end;(u:GetPropertyChangedSignal("\079\099\099\117\112\097\110\116")):connect(z);(O:GetAttributeChangedSignal("\065\108\108\111\119\067\097\114\106\097\099\107\105\110\103")):Connect(z)H.Triggered:connect(function(H)if u then if k(u)then R(H,u)elseif not u:FindFirstChild("\083\101\097\116\087\101\108\100")or carjackingEnabled(O)then if H.Character~=nil then local k=H.Character:FindFirstChild("\072\117\109\097\110\111\105\100\082\111\111\116\080\097\114\116")local R=H.Character:FindFirstChild("\072\117\109\097\110\111\105\100")if k then local H=(k.Position-u.Position).magnitude if H<=MAX_SEATING_DISTANCE then if u.Occupant then local k=u.Occupant.Parent for u,R in pairs(k.HumanoidRootPart:GetJoints())do if R.Name=="\083\101\097\116\087\101\108\100"then B(Players:GetPlayerFromCharacter(k),k,R.Part0,R)break end end end u:Sit(R)if u:FindFirstChild("\068\111\111\114\072\105\110\103\101")then if u.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 SeatInteractionCount[u]=SeatInteractionCount[u]and SeatInteractionCount[u]+1 or 1 u.DoorLatchWeld.Enabled=false u.DoorHinge.TargetAngle=DOOR_OPEN_ANGLE u.DoorHinge.AngularSpeed=DOOR_OPEN_SPEED playDoorSound(u,"\079\112\101\110\067\108\111\115\101")wait(DOOR_OPEN_TIME)SeatInteractionCount[u]=SeatInteractionCount[u]>1 and SeatInteractionCount[u]-1 or nil if SeatInteractionCount[u]==nil then u.DoorHinge.TargetAngle=0 while math.abs(u.DoorHinge.CurrentAngle)>.01 do wait()end u.DoorLatchWeld.Enabled=true end end end end end end end end)end end local t=getEffectsFolderFromSeat(u)if t then local k=t:FindFirstChild("\079\112\101\110\067\108\111\115\101\068\111\111\114")if k then(k:Clone()).Parent=u end end local X=nil local j=(u:GetPropertyChangedSignal("\079\099\099\117\112\097\110\116")):Connect(function()if u.Occupant then if H then X=u.Occupant X=Players:GetPlayerFromCharacter(X.Parent)or X H(X,u)end elseif O then O(X,u)X=nil end end)u.AncestryChanged:connect(function()if not u:IsDescendantOf(game)then j:Disconnect()end end)end return VehicleSeating

local function isFlipped(Seat)
	local UpVector = Seat.CFrame.upVector
	local Angle = math.deg(math.acos(UpVector:Dot(Vector3.new(0, 1, 0))))
	return Angle >= MIN_FLIP_ANGLE
end

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
			playDoorSound(seat, "OpenClose")
		end
		--Record the interaction
		SeatInteractionCount[seat] = SeatInteractionCount[seat] and SeatInteractionCount[seat] + 1 or 1

		task.wait(0.1)
		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

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

		task.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
					task.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.EjectCharacter(character)
	if character and character.HumanoidRootPart then
		for _, weld in pairs(character.HumanoidRootPart:GetJoints()) do
			if weld.Name == "SeatWeld" then
				ExitSeat(Players:GetPlayerFromCharacter(character), character, weld.Part0, weld)
				break
			end
		end
	end
end

function VehicleSeating.SetRemotesFolder(remotes)
	RemotesFolder = remotes

	--Detect exit seat requests
	RemotesFolder:FindFirstChild("ExitSeat").OnServerEvent:Connect(function(player)
		if player.Character then
			local character = player.Character
			VehicleSeating.EjectCharacter(character)
		end
	end)

	--Detect force exit seat requests
	RemotesFolder:FindFirstChild("ForceExitSeat").OnServerEvent:Connect(function(seatName)
		local chassis = PackagedVehicle:FindFirstChild("Chassis")
		if chassis then
			local seat = chassis:FindFirstChild(seatName)
			if seat and seat.Occupant then
				local occupantCharacter = seat.Occupant.Parent
				VehicleSeating.EjectCharacter(occupantCharacter)
			end
		end
	end)
end

function VehicleSeating.SetBindableEventsFolder(bindables)
	local BindableEventsFolder = bindables

	--Detect force exit seat requests
	BindableEventsFolder:FindFirstChild("ForceExitSeat").Event:Connect(function(seatName)
		local chassis = PackagedVehicle:FindFirstChild("Chassis")
		if chassis then
			local seat = chassis:FindFirstChild(seatName)
			if seat and seat.Occupant then
				local occupantCharacter = seat.Occupant.Parent
				VehicleSeating.EjectCharacter(occupantCharacter)
			end
		end
	end)
end

function VehicleSeating.AddSeat(seat, enterCallback, exitCallback)
	local promptLocation = seat:FindFirstChild("PromptLocation")
	if promptLocation then
		local proximityPrompt = promptLocation:FindFirstChildWhichIsA("ProximityPrompt")
		if proximityPrompt then
			local vehicleObj = getVehicleObject()
			
			if vehicleObj:GetAttribute("Health") <= 0 then return end
			
			local function setCarjackPrompt()
				if seat.Occupant and not carjackingEnabled(vehicleObj) then
					proximityPrompt.Enabled = false
				else
					proximityPrompt.Enabled = true
				end
			end

			seat:GetPropertyChangedSignal("Occupant"):connect(setCarjackPrompt)
			vehicleObj:GetAttributeChangedSignal("AllowCarjacking"):Connect(setCarjackPrompt)

			proximityPrompt.Triggered:connect(function(Player)
				if seat then
					if isFlipped(seat) then
						FlipSeat(Player, seat)

					elseif not seat:FindFirstChild("SeatWeld") or carjackingEnabled(vehicleObj) 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
									if seat.Occupant then
										local occupantCharacter = seat.Occupant.Parent
										for _, weld in pairs(occupantCharacter.HumanoidRootPart:GetJoints()) do
											if weld.Name == "SeatWeld" then
												ExitSeat(Players:GetPlayerFromCharacter(occupantCharacter), occupantCharacter, weld.Part0, weld)
												break
											end
										end
									end
									seat:Sit(humanoid)
									
									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

										playDoorSound(seat, "OpenClose")

										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
							end
						end
					end
				end
			end)
		end
	end

	local effectsFolder = getEffectsFolderFromSeat(seat)
	if effectsFolder then
		-- set open/close Sound
		local openclose = effectsFolder:FindFirstChild("OpenCloseDoor")
		if openclose then
			openclose:Clone().Parent = seat
		end
	end

	local currentOccupant = nil
	local occupantChangedConn = seat:GetPropertyChangedSignal("Occupant"):Connect(function()
		if seat.Occupant then
			if enterCallback then
				currentOccupant = seat.Occupant
				currentOccupant = Players:GetPlayerFromCharacter(currentOccupant.Parent) or currentOccupant
				enterCallback(currentOccupant, seat)
			end
		elseif exitCallback then
			exitCallback(currentOccupant, seat)
			currentOccupant = 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
