return function(Plasma)
	local create = Plasma.create

	local Item = Plasma.widget(function(text, selected, icon, sideText, barWidth, index)
		local clicked, setClicked = Plasma.useState(false)
		local rightClicked, setRightClicked = Plasma.useState(false)
		local hovered, setHovered = Plasma.useState(false)
		local style = Plasma.useStyle()

		local refs = Plasma.useInstance(function(ref)
			local button = create("TextButton", {
				[ref] = "button",
				AutoButtonColor = false,
				Size = UDim2.new(1, 0, 0, 25),
				Text = "",

				create("UICorner", {
					CornerRadius = UDim.new(0, 8),
				}),

				create("UIPadding", {
					PaddingBottom = UDim.new(0, 0),
					PaddingLeft = UDim.new(0, 8),
					PaddingRight = UDim.new(0, 8),
					PaddingTop = UDim.new(0, 0),
				}),

				create("Frame", {
					[ref] = "container",
					BackgroundTransparency = 1,
					Size = UDim2.new(1, 0, 1, 0),

					create("UIListLayout", {
						SortOrder = Enum.SortOrder.LayoutOrder,
						FillDirection = Enum.FillDirection.Horizontal,
						Padding = UDim.new(0, 10),
					}),

					create("TextLabel", {
						[ref] = "index",
						Name = "index",
						AutomaticSize = Enum.AutomaticSize.X,
						Size = UDim2.new(0, 0, 1, 0),
						BackgroundTransparency = 1,
						Text = index,
						TextXAlignment = Enum.TextXAlignment.Left,
						TextSize = 11,
						TextColor3 = style.mutedTextColor,
						Font = Enum.Font.Gotham,
						Visible = index ~= nil,
					}),

					create("TextLabel", {
						Name = "Icon",
						BackgroundTransparency = 1,
						Size = UDim2.new(0, 22, 1, 0),
						Text = icon,
						TextXAlignment = Enum.TextXAlignment.Left,
						TextSize = 16,
						TextColor3 = style.textColor,
						Font = Enum.Font.GothamBold,
					}),

					create("TextLabel", {
						[ref] = "mainText",
						AutomaticSize = Enum.AutomaticSize.X,
						BackgroundTransparency = 1,
						Size = UDim2.new(0, 0, 1, 0),
						Text = text,
						RichText = true,
						TextXAlignment = Enum.TextXAlignment.Left,
						TextSize = 13,
						TextColor3 = style.textColor,
						Font = Enum.Font.Gotham,
						TextTruncate = Enum.TextTruncate.AtEnd,

						create("UISizeConstraint", {
							MaxSize = Vector2.new(165, math.huge),
						}),
					}),

					create("TextLabel", {
						[ref] = "sideText",
						BackgroundTransparency = 1,
						AutomaticSize = Enum.AutomaticSize.X,
						Size = UDim2.new(0, 0, 1, 0),
						Text = "",
						TextXAlignment = Enum.TextXAlignment.Left,
						TextSize = 11,
						RichText = true,
						TextColor3 = style.mutedTextColor,
						Font = Enum.Font.Gotham,
					}),
				}),

				create("UIListLayout", {
					SortOrder = Enum.SortOrder.LayoutOrder,
				}),

				create("Frame", {
					[ref] = "bar",
					BackgroundColor3 = style.mutedTextColor,
					BorderSizePixel = 0,
					LayoutOrder = 1,
					ZIndex = 2,
				}),

				InputBegan = function(input: InputObject)
					if input.UserInputType == Enum.UserInputType.MouseButton1 then
						setClicked(true)
					elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
						setRightClicked(true)
					end
				end,

				MouseEnter = function()
					setHovered(true)
				end,

				MouseLeave = function()
					setHovered(false)
				end,
			})

			return button
		end)

		Plasma.useEffect(function()
			refs.mainText.Text = text
			refs.index.Text = index or ""
			refs.button.container.Icon.Text = icon or ""
			refs.button.container.Icon.Visible = icon ~= nil
		end, text, icon, index)

		refs.button.container.sideText.Visible = sideText ~= nil
		refs.button.container.sideText.Text = if sideText ~= nil then sideText else ""
		refs.button.container.sideText.TextColor3 = if selected then style.textColor else style.mutedTextColor
		refs.mainText.TextTruncate = sideText and Enum.TextTruncate.AtEnd or Enum.TextTruncate.None

		refs.button.bar.Size = UDim2.new(barWidth or 0, 0, 0, 1)

		Plasma.useEffect(function()
			refs.button.BackgroundColor3 = if selected
				then style.primaryColor
				elseif hovered then style.bg1
				else style.bg2

			refs.index.TextColor3 = if selected or hovered then style.textColor else style.mutedTextColor
		end, selected, hovered)

		return {
			clicked = function()
				if clicked then
					setClicked(false)
					return true
				end

				return false
			end,
			rightClicked = function()
				if rightClicked then
					setRightClicked(false)
					return true
				end

				return false
			end,
			hovered = function()
				return hovered
			end,
		}
	end)

	return Plasma.widget(function(items)
		Plasma.useInstance(function()
			local frame = create("Frame", {
				BackgroundTransparency = 1,
				Size = UDim2.new(1, 0, 0, 0),

				create("UIListLayout", {
					SortOrder = Enum.SortOrder.LayoutOrder,
					Padding = UDim.new(0, 2),
				}),
			})

			Plasma.automaticSize(frame, {
				axis = Enum.AutomaticSize.Y,
			})

			return frame
		end)

		local selected
		local rightClicked
		local hovered

		for _, item in items do
			local buttonInList = Item(item.text, item.selected, item.icon, item.sideText, item.barWidth, item.index)

			if buttonInList:clicked() then
				selected = item
			end

			if buttonInList:rightClicked() then
				rightClicked = item
			end

			if buttonInList:hovered() then
				hovered = item
			end
		end

		return {
			selected = function()
				return selected
			end,
			rightClicked = function()
				return rightClicked
			end,
			hovered = function()
				return hovered
			end,
		}
	end)
end


local CollectionService = game:GetService("CollectionService")

type Options = {
	tag: string,
	backgroundTransparency: number?,
}

return function(plasma)
	local create = plasma.create

	return plasma.widget(function(text, options: Options)
		local refs = plasma.useInstance(function(ref)
			local style = plasma.useStyle()

			create("TextLabel", {
				[ref] = "label",
				TextXAlignment = Enum.TextXAlignment.Left,
				TextYAlignment = Enum.TextYAlignment.Top,
				TextSize = 13,
				RichText = true,
				BorderSizePixel = 0,
				Font = Enum.Font.Code,
				TextStrokeTransparency = 0.5,
				TextColor3 = Color3.new(1, 1, 1),
				BackgroundTransparency = options.backgroundTransparency or 0.2,
				BackgroundColor3 = style.bg1,
				AutomaticSize = Enum.AutomaticSize.XY,
				ZIndex = 100,

				create("UIPadding", {
					PaddingBottom = UDim.new(0, 4),
					PaddingLeft = UDim.new(0, 4),
					PaddingRight = UDim.new(0, 4),
					PaddingTop = UDim.new(0, 4),
				}),

				create("UICorner"),

				create("UIStroke", {
					ApplyStrokeMode = Enum.ApplyStrokeMode.Border,
				}),
			})

			CollectionService:AddTag(ref.label, options.tag)

			return ref.label
		end)

		refs.label.Text = text
	end)
end


local formatTableModule = require(script.Parent.Parent.formatTable)
local formatTable = formatTableModule.formatTable

return function(plasma)
	return plasma.widget(function(objectStack, custom)
		local closed = plasma
			.window({
				title = "Inspect",
				closable = true,
			}, function()
				plasma.row({ padding = 5 }, function()
					for i, object in objectStack do
						if
							custom
								.link(object.key, {
									icon = object.icon or "{}",
								})
								:clicked()
						then
							local difference = #objectStack - i

							for _ = 1, difference do
								table.remove(objectStack, #objectStack)
							end
						end

						if i < #objectStack then
							custom.link("▶", {
								disabled = true,
							})
						end
					end
				end)

				local items = {}

				for key, value in pairs(objectStack[#objectStack].value) do
					local valueItem

					if type(value) == "table" then
						valueItem = function()
							if
								custom
									.link(formatTable(value), {
										font = Enum.Font.Code,
									})
									:clicked()
							then
								table.insert(objectStack, {
									key = if type(key) == "table" then formatTable(key) else tostring(key),
									value = value,
								})
							end
						end
					else
						valueItem = tostring(value)
					end

					table.insert(items, {
						tostring(key),
						valueItem,
					})
				end

				plasma.useKey(tostring(objectStack[#objectStack].key) .. ":" .. #objectStack)

				if #items == 0 then
					return plasma.label("(empty table)")
				end

				plasma.table(items, {
					font = Enum.Font.Code,
				})
				return nil
			end)
			:closed()

		if closed then
			table.clear(objectStack)
		end
	end)
end


local formatTableModule = require(script.Parent.Parent.formatTable)
local getAllComponentData = require(script.Parent.Parent.getAllComponentData)
local formatTable = formatTableModule.formatTable

local BY_COMPONENT_NAME = "ComponentName"
local BY_ENTITY_COUNT = "EntityCount"

return function(plasma)
	return plasma.widget(function(debugger, world, objectStack)
		local style = plasma.useStyle()

		local cache, setCache = plasma.useState()

		local sortType, setSortType = plasma.useState(BY_COMPONENT_NAME)
		local isAscendingOrder, setIsAscendingOrder = plasma.useState(true)

		local skipIntersections, setSkipIntersections = plasma.useState(true)
		local debugComponent, setDebugComponent = plasma.useState()

		local closed = plasma
			.window({
				title = "World inspect",
				closable = true,
			}, function()
				if not cache or os.clock() - cache.createdTime > debugger.componentRefreshFrequency then
					cache = {
						createdTime = os.clock(),
						uniqueComponents = {},
						emptyEntities = 0,
					}

					setCache(cache)

					for _, entityData in world do
						if next(entityData) == nil then
							cache.emptyEntities += 1
						else
							for component in entityData do
								cache.uniqueComponents[component] = (cache.uniqueComponents[component] or 0) + 1
							end
						end
					end
				end

				plasma.row({
					verticalAlignment = Enum.VerticalAlignment.Center,
				}, function()
					plasma.heading("SIZE:")
					plasma.label(
						`{world:size()} {if cache.emptyEntities > 0 then `({cache.emptyEntities} empty)` else ""}`
					)

					if plasma.button("View Raw"):clicked() then
						table.clear(objectStack)
						objectStack[1] = {
							value = world,
							key = "Raw World",
						}
					end
				end)

				plasma.row({ padding = 15 }, function()
					if plasma.checkbox("Show intersections", { checked = not skipIntersections }):clicked() then
						setSkipIntersections(not skipIntersections)
					end
				end)

				local items = {}
				for component, count in cache.uniqueComponents do
					table.insert(items, {
						count,
						tostring(component),
						selected = debugComponent == component,
						component = component,
					})
				end

				local indexForSort = if sortType == BY_ENTITY_COUNT then 1 else 2

				table.sort(items, function(a, b)
					if isAscendingOrder then
						return a[indexForSort] < b[indexForSort]
					end

					return a[indexForSort] > b[indexForSort]
				end)

				local arrow = if isAscendingOrder then "▲" else "▼"
				local countHeading = `{if sortType == BY_ENTITY_COUNT then arrow else ""} Count `
				local componentHeading = `{if sortType == BY_COMPONENT_NAME then arrow else ""} Component`
				local headings = { countHeading, componentHeading }
				table.insert(items, 1, headings)

				plasma.row({ padding = 30 }, function()
					local worldInspectTable = plasma.table(items, {
						width = 200,
						headings = true,
						selectable = true,
						font = Enum.Font.Code,
					})

					local selectedHeading = worldInspectTable:selectedHeading()

					if headings[selectedHeading] == headings[1] then
						if sortType == BY_ENTITY_COUNT then
							setIsAscendingOrder(not isAscendingOrder)
						else
							setSortType(BY_ENTITY_COUNT)
						end
					elseif headings[selectedHeading] == headings[2] then
						if sortType == BY_COMPONENT_NAME then
							setIsAscendingOrder(not isAscendingOrder)
						else
							setSortType(BY_COMPONENT_NAME)
						end
					end

					local selectedRow = worldInspectTable:selected()

					if selectedRow then
						setDebugComponent(selectedRow.component)
					end

					if debugComponent then
						local items = { { "Entity ID", tostring(debugComponent) } }
						local intersectingComponents = {}

						local intersectingData = {}

						for entityId, data in world:query(debugComponent) do
							table.insert(items, {
								entityId,
								formatTable(data),

								selected = debugger.debugEntity == entityId,
							})

							intersectingData[entityId] = {}

							if skipIntersections then
								continue
							end

							for component, value in getAllComponentData(world, entityId) do
								if component == debugComponent then
									continue
								end

								local index = table.find(intersectingComponents, component)

								if not index then
									table.insert(intersectingComponents, component)

									index = #intersectingComponents
								end

								intersectingData[entityId][index] = value
							end
						end

						for i, item in items do
							if i == 1 then
								for _, component in intersectingComponents do
									table.insert(item, tostring(component))
								end

								continue
							end

							for i = 1, #intersectingComponents do
								local data = intersectingData[item[1]][i]

								table.insert(item, if data then formatTable(data) else "")
							end
						end

						plasma.useKey(tostring(debugComponent))

						local tableWidget = plasma.table(items, {
							font = Enum.Font.Code,
							selectable = true,
							headings = true,
						})

						local selectedRow = tableWidget:selected()
						local hovered = tableWidget:hovered()

						if selectedRow then
							debugger.debugEntity = selectedRow[1]
						end

						if hovered then
							local entityId = hovered[1]

							if debugger.debugEntity == entityId or not world:contains(entityId) then
								return
							end

							if debugger.findInstanceFromEntity then
								local model = debugger.findInstanceFromEntity(entityId)

								if model then
									plasma.highlight(model, {
										fillColor = style.primaryColor,
									})
								end
							end
						end
					end
				end)
			end)
			:closed()

		if closed then
			return closed
		end
		return nil
	end)
end


local topoRuntime = require(script.Parent.Parent.topoRuntime)
local format = require(script.Parent.Parent.debugger.formatTable)

--[=[
	@within Matter

	:::info Topologically-aware function
	This function is only usable if called within the context of [`Loop:begin`](/api/Loop#begin).
	:::

	@param ... any

	Logs some text. Readable in the Matter debugger.
]=]
local function log(...)
	local state = topoRuntime.useFrameState()

	if state.logs == nil then
		return
	end

	local segments = {}

	for i = 1, select("#", ...) do
		local value = select(i, ...)

		if type(value) == "table" then
			segments[i] = format.formatTable(value)
		else
			segments[i] = tostring(value)
		end
	end

	table.insert(state.logs, table.concat(segments, " "))

	if #state.logs > 100 then
		table.remove(state.logs, 1)
	end
end

return log


local topoRuntime = require(script.Parent.Parent.topoRuntime)

--[=[
	@within Matter

	:::info Topologically-aware function
	This function is only usable if called within the context of [`Loop:begin`](/api/Loop#begin).
	:::

	Returns the `os.clock()` time delta between the start of this and last frame.
]=]
local function useDeltaTime(): number
	local state = topoRuntime.useFrameState()

	return state.deltaTime
end

return useDeltaTime


local topoRuntime = require(script.Parent.Parent.topoRuntime)
local Queue = require(script.Parent.Parent.Queue)

local EVENT_CONNECT_METHODS = { "Connect", "on", "connect" }
local CONNECTION_DISCONNECT_METHODS = { "Disconnect", "Destroy", "disconnect", "destroy" }

local function connect(object, callback, event)
	local eventObject = object

	if typeof(event) == "RBXScriptSignal" or type(event) == "table" then
		eventObject = event
	elseif type(event) == "string" then
		eventObject = object[event]
	end

	if type(eventObject) == "function" then
		return eventObject(object)
	elseif typeof(eventObject) == "RBXScriptSignal" then
		return eventObject:Connect(callback)
	end

	if type(eventObject) == "table" then
		for _, method in EVENT_CONNECT_METHODS do
			if type(eventObject[method]) ~= "function" then
				continue
			end

			return eventObject[method](eventObject, callback)
		end
	end

	error(
		"Couldn't connect to event as no valid connect methods were found! Ensure the passed event has a 'Connect' or an 'on' method!"
	)
end

local function disconnect(connection)
	if connection == nil then
		return
	end

	if type(connection) == "function" then
		connection()
		return
	end

	for _, method in CONNECTION_DISCONNECT_METHODS do
		if type(connection[method]) ~= "function" then
			continue
		end

		connection[method](connection)
		break
	end
end

local function validateConnection(connection)
	if typeof(connection) == "function" or typeof(connection) == "RBXScriptConnection" then
		return
	end

	for _, method in CONNECTION_DISCONNECT_METHODS do
		if type(connection) ~= "table" or connection[method] == nil then
			continue
		end

		return
	end

	error("Ensure passed event returns a cleanup function, or a table with a 'Disconnect' or a 'Destroy' method!")
end

local function cleanup(storage)
	disconnect(storage.connection)
	storage.queue = nil
end

--[=[
	@type ConnectionObject {Disconnect: (() -> ())?, Destroy: (() - >())?, disconnect: (() -> ())?, destroy: (() -> ())?} | () -> ()
	@within Matter

	A connection object returned by a custom event must be either a table with any of the following methods, or a cleanup function.
]=]

--[=[
	@interface CustomEvent
	@within Matter
	.Connect ((...) -> ConnectionObject)?
	.on ((...) -> ConnectionObject)?
	.connect ((...) -> ConnectionObject)?

	A custom event must have any of these 3 methods.
]=]

--[=[
	@within Matter
	:::info Topologically-aware function
	This function is only usable if called within the context of [`Loop:begin`](/api/Loop#begin).
	:::

	Collects events that fire during the frame and allows iteration over event arguments.

	```lua
	for _, player in ipairs(Players:GetPlayers()) do
		for i, character in useEvent(player, "CharacterAdded") do
			world:spawn(
				Components.Target(),
				Components.Model({
					model = character,
				})
			)
		end
	end
	```

	Returns an iterator function that returns an ever-increasing number, starting at 1, followed by any event arguments
	from the specified event.

	Events are returned in the order that they were fired.

	:::caution
	`useEvent` keys storage uniquely identified by **the script and line number** `useEvent` was called from, and the
	first parameter (instance). If the second parameter, `event`, is not equal to the event passed in for this unique
	storage last frame, the old event is disconnected from and the new one is connected in its place.

	Tl;dr: on a given line, you should hard-code a single event to connect to. Do not dynamically change the event with
	a variable. Dynamically changing the first parameter (instance) is fine.

	```lua
	for _, instance in pairs(someTable) do
		for i, arg1, arg2 in useEvent(instance, "Touched") do -- This is ok
		end
	end

	for _, instance in pairs(someTable) do
		local event = getEventSomehow()
		for i, arg1, arg2 in useEvent(instance, event) do -- PANIC! This is NOT OK
		end
	end
	```
	:::

	If `useEvent` ceases to be called on the same line with the same instance and event, the event connection is
	disconnected from automatically.

	You can also pass the actual event object instead of its name as the second parameter:

	```lua
	useEvent(instance, instance.Touched)
	useEvent(instance, instance:GetPropertyChangedSignal("Name"))
	```

	`useEvent` supports custom events as well, so you can pass in an object with a `Connect`, `connect` or an `on` method.
	The object returned by any event must either be a cleanup function, or a table with a `Disconnect` or a `Destroy`
	method so that `useEvent` can later clean the event up when needed.	See [ConnectionObject] for more information.

	@param instance Instance | { [string]: CustomEvent } | CustomEvent -- The instance or the custom event, or a table that has the event you want to connect to
	@param event string | RBXScriptSignal | CustomEvent -- The name of, or the actual event that you want to connect to
]=]
local function useEvent(instance, event): () -> (number, ...any)
	assert(instance ~= nil, "Instance is nil")
	assert(event ~= nil, "Event is nil")

	local storage = topoRuntime.useHookState(instance, cleanup)

	if storage.event ~= event then
		if storage.event then
			disconnect(storage.connection)
			warn("useEvent event changed:", storage.event, "->", event)
			table.clear(storage)
		end

		local queue = Queue.new()
		storage.queue = queue
		storage.event = event

		local connection = connect(instance, function(...)
			queue:pushBack(table.pack(...))
		end, event)

		validateConnection(connection)
		storage.connection = connection
	end

	local index = 0
	return function(): any
		index += 1

		local arguments = storage.queue:popFront()

		if arguments then
			return index, unpack(arguments, 1, arguments.n)
		end
		return
	end
end

return useEvent


local topoRuntime = require(script.Parent.Parent.topoRuntime)

local function cleanup(storage)
	return os.clock() < storage.expiry
end

--[=[
	@within Matter

	:::info Topologically-aware function
	This function is only usable if called within the context of [`Loop:begin`](/api/Loop#begin).
	:::

	Utility for easy time-based throttling.

	Accepts a duration, and returns `true` if it has been that long since the last time this function returned `true`.
	Always returns `true` the first time.

	This function returns unique results keyed by script and line number. Additionally, uniqueness can be keyed by a
	unique value, which is passed as a second parameter. This is useful when iterating over a query result, as you can
	throttle doing something to each entity individually.

	```lua
	if useThrottle(1) then -- Keyed by script and line number only
		print("only prints every second")
	end

	for id, enemy in world:query(Enemy) do
		if useThrottle(5, id) then -- Keyed by script, line number, and the entity id
			print("Recalculate target...")
		end
	end
	```

	@param seconds number -- The number of seconds to throttle for
	@param discriminator? any -- A unique value to additionally key by
	@return boolean -- returns true every x seconds, otherwise false
]=]
local function useThrottle(seconds, discriminator)
	local storage = topoRuntime.useHookState(discriminator, cleanup)

	if storage.time == nil or os.clock() - storage.time >= seconds then
		storage.time = os.clock()
		storage.expiry = os.clock() + seconds
		return true
	end

	return false
end

return useThrottle


--[=[
	@class Matter

	Matter. It's what everything is made out of.
]=]

--[=[
	@within Matter
	@prop World World
]=]

--[=[
	@within Matter
	@prop Loop Loop
]=]

--[=[
	@within Matter
	@prop Debugger Debugger
]=]

--[=[
	@within Matter
	@prop None None

	A value should be interpreted as nil when merging dictionaries.

	`Matter.None` is used by [`Component:patch`](/api/Component#patch).
]=]

--[=[
	@within Matter
	@function component
	@param name? string -- Optional name for debugging purposes
	@param defaultData? {} -- Default data that will be merged with data passed to the component when created
	@return Component -- Your new type of component

	Creates a new type of component. Call the component as a function to create an instance of that component.

	If `defaultData` is specified, it will be merged with data passed to the component when the component instance is
	created. Note that this is not *fallback* data: if you later remove a field from a component instance that is
	specified in the default data, it won't fall back to the value specified in default data.

	```lua
	-- Component:
	local MyComponent = Matter.component("My component")

	-- component instance:
	local myComponentInstance = MyComponent({
		some = "data"
	})
	```
]=]

local immutable = require(script.immutable)
local World = require(script.World)
local Loop = require(script.Loop)
local newComponent = require(script.component).newComponent
local topoRuntime = require(script.topoRuntime)

export type World = World.World
export type Loop = typeof(Loop.new())

return table.freeze({
	World = World,
	Loop = Loop,

	component = newComponent,

	useEvent = require(script.hooks.useEvent),
	useDeltaTime = require(script.hooks.useDeltaTime),
	useThrottle = require(script.hooks.useThrottle),
	log = require(script.hooks.log),
	useHookState = topoRuntime.useHookState,
	useCurrentSystem = topoRuntime.useCurrentSystem,

	merge = immutable.merge,
	None = immutable.None,

	Debugger = require(script.debugger.debugger),
})


local MAX_SAMPLES = 60

local function addSample(samples, value)
	samples[samples.index or 1] = value
	samples.index = if samples.index then (samples.index % MAX_SAMPLES) + 1 else 1
end

local function getAverage(samples)
	local sum = 0

	for i = 1, #samples do
		sum += samples[i]
	end

	return sum / #samples
end

return {
	addSample = addSample,
	getAverage = getAverage,
}


local stack = {}

local function newStackFrame(node)
	return {
		node = node,
		accessedKeys = {},
	}
end

local function cleanup()
	local currentFrame = stack[#stack]

	for baseKey, state in pairs(currentFrame.node.system) do
		for key, value in pairs(state.storage) do
			if not currentFrame.accessedKeys[baseKey] or not currentFrame.accessedKeys[baseKey][key] then
				local cleanupCallback = state.cleanupCallback

				if cleanupCallback then
					local shouldAbortCleanup = cleanupCallback(value)

					if shouldAbortCleanup then
						continue
					end
				end

				state.storage[key] = nil
			end
		end
	end
end

local function start(node, fn)
	table.insert(stack, newStackFrame(node))
	fn()
	cleanup()
	table.remove(stack, #stack)
end

local function withinTopoContext()
	return #stack ~= 0
end

local function useFrameState()
	return stack[#stack].node.frame
end

local function useCurrentSystem()
	if #stack == 0 then
		return
	end

	return stack[#stack].node.currentSystem
end

--[=[
	@within Matter

	:::tip
	**Don't use this function directly in your systems.**

	This function is used for implementing your own topologically-aware functions. It should not be used in your
	systems directly. You should use this function to implement your own utilities, similar to `useEvent` and
	`useThrottle`.
	:::

	`useHookState` does one thing: it returns a table. An empty, pristine table. Here's the cool thing though:
	it always returns the *same* table, based on the script and line where *your function* (the function calling
	`useHookState`) was called.

	### Uniqueness

	If your function is called multiple times from the same line, perhaps within a loop, the default behavior of
	`useHookState` is to uniquely identify these by call count, and will return a unique table for each call.

	However, you can override this behavior: you can choose to key by any other value. This means that in addition to
	script and line number, the storage will also only return the same table if the unique value (otherwise known as the
	"discriminator") is the same.

	### Cleaning up
	As a second optional parameter, you can pass a function that is automatically invoked when your storage is about
	to be cleaned up. This happens when your function (and by extension, `useHookState`) ceases to be called again
	next frame (keyed by script, line number, and discriminator).

	Your cleanup callback is passed the storage table that's about to be cleaned up. You can then perform cleanup work,
	like disconnecting events.

	*Or*, you could return `true`, and abort cleaning up altogether. If you abort cleanup, your storage will stick
	around another frame (even if your function wasn't called again). This can be used when you know that the user will
	(or might) eventually call your function again, even if they didn't this frame. (For example, caching a value for
	a number of seconds).

	If cleanup is aborted, your cleanup function will continue to be called every frame, until you don't abort cleanup,
	or the user actually calls your function again.

	### Example: useThrottle

	This is the entire implementation of the built-in `useThrottle` function:

	```lua
	local function cleanup(storage)
		return os.clock() < storage.expiry
	end

	local function useThrottle(seconds, discriminator)
		local storage = useHookState(discriminator, cleanup)

		if storage.time == nil or os.clock() - storage.time >= seconds then
			storage.time = os.clock()
			storage.expiry = os.clock() + seconds
			return true
		end

		return false
	end
	```

	A lot of talk for something so simple, right?

	@param discriminator? any -- A unique value to additionally key by
	@param cleanupCallback (storage: {}) -> boolean? -- A function to run when the storage for this hook is cleaned up
]=]
local function useHookState(discriminator, cleanupCallback): {}
	local file, line = debug.info(3, "sl")
	local fn = debug.info(2, "f")

	local baseKey = string.format("%s:%s:%d", tostring(fn), file, line)

	local currentFrame = stack[#stack]

	if currentFrame == nil then
		error("Attempt to access topologically-aware storage outside of a Loop-system context.", 3)
	end

	if not currentFrame.accessedKeys[baseKey] then
		currentFrame.accessedKeys[baseKey] = {}
	end

	local accessedKeys = currentFrame.accessedKeys[baseKey]

	local key = #accessedKeys

	if discriminator ~= nil then
		if type(discriminator) == "number" then
			discriminator = tostring(discriminator)
		end

		key = discriminator
	end

	accessedKeys[key] = true

	if not currentFrame.node.system[baseKey] then
		currentFrame.node.system[baseKey] = {
			storage = {},
			cleanupCallback = cleanupCallback,
		}
	end

	local storage = currentFrame.node.system[baseKey].storage

	if not storage[key] then
		storage[key] = {}
	end

	return storage[key]
end

return {
	start = start,
	useHookState = useHookState,
	useFrameState = useFrameState,
	useCurrentSystem = useCurrentSystem,
	withinTopoContext = withinTopoContext,
}


local ReplicatedStorage = game:GetService("ReplicatedStorage")

require(ReplicatedStorage.TestEZ).TestBootstrap:run({
	ReplicatedStorage.Matter,
	nil,
	{
		noXpcallByDefault = true,
	},
})


local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Matter = require(ReplicatedStorage.Matter)
local immutable = require(ReplicatedStorage.Matter.immutable)

-- 500 entities
-- 2-30 components on each entity
-- 300 unique components
-- 200 systems
-- 1-10 components to query per system

local startTime = os.clock()

local world = Matter.World.new()

local components = {}

for i = 1, 300 do -- 300 components
	components[i] = Matter.component("Component " .. i)
end

local archetypes = {}
for i = 1, 50 do -- 50 archetypes
	local archetype = {}

	for _ = 1, math.random(2, 30) do
		local componentId = math.random(1, #components)

		table.insert(archetype, components[componentId])
	end

	archetypes[i] = archetype
end

for _ = 1, 1000 do -- 1000 entities in the world
	local componentsToAdd = {}

	local archetypeId = math.random(1, #archetypes)
	for _, component in ipairs(archetypes[archetypeId]) do
		componentsToAdd[component] = component({
			DummyData = math.random(1, 5000),
		})
	end

	world:spawn(unpack(immutable.values(componentsToAdd)))
end

local contiguousComponents = immutable.values(components)
local systemComponentsToQuery = {}

for _ = 1, 200 do -- 200 systems
	local numComponentsToQuery = math.random(1, 10)
	local componentsToQuery = {}

	for _ = 1, numComponentsToQuery do
		table.insert(componentsToQuery, contiguousComponents[math.random(1, #contiguousComponents)])
	end

	table.insert(systemComponentsToQuery, componentsToQuery)
end

local worldCreateTime = os.clock() - startTime
local results = {}
startTime = os.clock()

RunService.Heartbeat:Connect(function()
	local added = 0
	local systemStartTime = os.clock()
	debug.profilebegin("systems")
	for _, componentsToQuery in ipairs(systemComponentsToQuery) do
		debug.profilebegin("system")
		for entityId, firstComponent in world:query(unpack(componentsToQuery)) do
			world:insert(
				entityId,
				firstComponent:patch({
					DummyData = firstComponent.DummyData + 1,
				})
			)
			added += 1
		end
		world:optimizeQueries()
		debug.profileend()
	end
	debug.profileend()

	if os.clock() - startTime < 4 then
		-- discard first 4 seconds
		return
	end

	if results == nil then
		return
	elseif #results < 1000 then
		table.insert(results, os.clock() - systemStartTime)
	else
		print("added", added)
		print("World created in", worldCreateTime * 1000, "ms")
		local sum = 0
		for _, result in ipairs(results) do
			sum += result
		end
		print(("Average frame time: %fms"):format((sum / #results) * 1000))

		results = nil

		local n = 0

		for _ in pairs(world._entityArchetypeCache) do
			n += 1
		end

		print(
			("%d entities\n%d components\n%d systems\n%d archetypes"):format(
				world:size(),
				#components,
				#systemComponentsToQuery,
				n
			)
		)
	end
end)


--[[
		             _____ _                   _
	     /\         / ____| |                 (_)
	    /  \ ______| |    | |__   __ _ ___ ___ _ ___
	   / /\ \______| |    | '_ \ / _` / __/ __| / __|
	  / ____ \     | |____| | | | (_| \__ \__ \ \__ \
	 /_/    \_\     \_____|_| |_|\__,_|___/___/_|___/
	 Version 1.7
--]]
-- This is the CustomEngine module.
--[[

	I'll try to explain this in the best way possible.

	How this works is every frame/tick, the Drive sends data to this script if you have the CustomEngine functionality enabled.
	*To enable this, make sure this script is inside A-Chassis Tune, and Tune.CustomEngineCalculations = true
	The data consists of nearly every variable that the Drive creates.

		If you are familiar with the Drive values, here is an example:
		local _RPM = env._RPM

		*env stands for environment, as I'm setting the environment of the function to be the environment of the Drive.

	The purpose of this script is so that savvy A-Chassis enjoyers can build custom engines without digging around in the Drive.
	It's for the people between tuning professionals and programming nerds.
	It's also for the legends who may distribute their own custom engines to others in a simpler and easier manner.

	For the purposes of A-Chassis:
		To me, and to future members and developers, use this feature to add backwards compatibility to older versions of the engine.
		With that being said, try to change as little as possible to ensure this feature still works for the engines that are at least 4 years old.

]]

--[[Engine]]

-- // End
-- Here are the values that will get outputted back to the drive.
-- It's possible to add more values, but that requires editting the Drive directly, defeating the whole purpose of this feature.
-- Most of these go unused if you are trying to build the engine from an older chassis.
-- *Keep in mind that until 1.6, torque calculations had the gear ratio factored in, so if you are building an engine, remember to factor out the gear ratios.
local engine = {}

local _Boost = 0
local _TBoost = 0
local _SBoost = 0

local _HP = 0
local _TQ = 0
local _NH = 0
local _NT = 0
local _EH = 0
local _ET = 0
local _TH = 0
local _TT = 0
local _SH = 0
local _ST = 0
local _BH = 0
local _BT = 0

--[[Power calculation prerequesites]]
-- Add the functions that don't run in the runtime loop here.
-- For example, the GetCurve functions from 1.5 and before.

--[[Loop]]
engine.run = function(env, dt)
	-- This is the runtime loop. It gets called every frame by the Drive. *Refer to the example from above.

	-- Everything above in this function can be replaced, but please keep the values below.
	engine._Boost = _Boost
	engine._TBoost = _TBoost
	engine._SBoost = _SBoost

	engine._HP = _HP
	engine._TQ = _TQ
	engine._NH = _NH
	engine._NT = _NT
	engine._EH = _EH
	engine._ET = _ET
	engine._TH = _TH
	engine._TT = _TT
	engine._SH = _SH
	engine._ST = _ST
	engine._BH = _BH
	engine._BT = _BT
end

return engine


--[[
       ___  _____
      / _ |/ ___/    	Avxnturador       	| Novena                            midmm5        |
     / __ / /__        	LuaInt             	| Novena
    /_/ |_\___/         Detomiks        	| Base Chassis

                    Version 1.7

	*Credit to VanillaEdge for this MiscWeld refresh.

--]]
local MiscWeld = {}

-- Create weld between two parts and optional motor setup
function MakeWeld(part1, part2, weldType, motorSpeed )
    if weldType == nil then weldType = "Weld" end
    local weld = Instance.new(weldType, part1)
    weld.Part0 = part1
    weld.Part1 = part2
    weld.C0 = part1.CFrame:inverse() * part1.CFrame
    weld.C1 = part2.CFrame:inverse() * part1.CFrame
    if weldType == "Motor" and motorSpeed ~= nil then
        weld.MaxVelocity = motorSpeed
    end
    return weld
end

-- Welds all parts within an instance to a target part
function ModelWeld(model, target)
    if model:IsA("BasePart") then
        MakeWeld(target, model, "Weld")
    end
    for _, part in model:GetDescendants() do
        if part:IsA("BasePart") then
            MakeWeld(part, target)
        end
    end
end

car = script.Parent.Parent.Parent
misc = car:WaitForChild("Misc")

---------------------------

--[[
    Usage Guide:
    Main anchor point is the DriveSeat <car.DriveSeat>

    1. MakeWeld: Connects two parts with a weld. Use for simple connections.
        - Syntax: MakeWeld(part1, part2, [weldType], [motorSpeed])
        - Example: MakeWeld(car.DriveSeat, misc.PassengerSeat) -- Default weld
                   MakeWeld(car.DriveSeat, misc.SteeringWheel, "Motor", 0.2) -- Motor with speed

    2. ModelWeld: Welds an entire model to a target part
        - Syntax: ModelWeld(model, targetPart)
        - Example: ModelWeld(misc.FrontBumper, car.DriveSeat)

    3. FolderWeld: Welds all parts within a folder to a target part
        - Syntax: FolderWeld(folder, targetPart)
        - Example: FolderWeld(misc.Accessories, car.DriveSeat)

]]

--[[Weld stuff below here]]

-- This is the default character offset for the driveseat
car.DriveSeat.ChildAdded:connect(function(child)
    if child.Name=="SeatWeld" and child:IsA("Weld") and game.Players:GetPlayerFromCharacter(child.Part1.Parent)~=nil then
        child.C0=CFrame.new(0,-.5,0)*CFrame.fromEulerAnglesXYZ(-(math.pi/2),0,0)*CFrame.Angles(math.rad(13),0,0)
    end
end)

---------------------------
return MiscWeld


--[[
		             _____ _                   _
	     /\         / ____| |                 (_)
	    /  \ ______| |    | |__   __ _ ___ ___ _ ___
	   / /\ \______| |    | '_ \ / _` / __/ __| / __|
	  / ____ \     | |____| | | | (_| \__ \__ \ \__ \
	 /_/    \_\     \_____|_| |_|\__,_|___/___/_|___/
	 Version 1.7
--]]

local car = script.Parent.Car.Value

-- [[Editable Values]]
local UseWeight			= false		-- If true, does not use body parts to apply drag and downforce.
local Slipstream		= false		-- Slipstream may lag on larger maps

local WindVolume       	= .6		-- Volume of wind at 200 km/h.
local BodyVolume		= .1		-- Volume of body sounds at 200 km/h.

local Drag				= .38		-- Drag coefficient of the car. (You will have to tinker with this value to get the actual top speed, as the calculations are inaccurate.)
local DragMult			= .8		-- Multiplier for drag. This is so that the Drag value is accurate to your car. This value will need trial and error.
local Slipstream		= 60		-- How much drag is reduced in slipstream (in percent)
local SlipstreamTime	= .7		-- Maximum amount of time you can be behind a car to gain slipstream.
local DownforceF		= 50		-- Front downforce in kgf at 200 km/h.
local DownforceR		= 70		-- Rear downforce in kgf at 200 km/h.

local Directory			= car.Body:FindFirstChild("Aerodynamics")
local SimulationRate 	= 1/30

--[[END]]
-- Probably don't touch stuff down here
local values = script.Parent.Values
local player = game.Players.LocalPlayer
local Units = require(car["A-Chassis Tune"].README.Units)
local RunService = game:GetService("RunService")
local weight = car.Body["#Weight"]

local _DragFactor = 1
local toKMH = (Units.Length_mm/1000) * 3.6
local fromKGF = 9.81*Units.Force_N

local CenterValue = values.Center
local VelocityValue = values.Velocity

task.wait() -- Add waits to try and reduce load on the client on startup.
-- Create our sounds, or use existing ones.
local wind = weight:FindFirstChild("WindSound") or Instance.new("Sound")
wind.Name = "WindSound"
wind.RollOffMaxDistance = 100
wind.RollOffMinDistance = 10
wind.RollOffMode = Enum.RollOffMode.InverseTapered
wind.Looped = true
wind.SoundId = "rbxassetid://188608071"
wind.Volume = 0
wind.Parent = weight
local eq = wind:FindFirstChild("EQ") or Instance.new("EqualizerSoundEffect")
eq.Name = "EQ"
eq.HighGain = 0
eq.LowGain = 5
eq.MidGain = -10
eq.Parent = wind
local body = weight:FindFirstChild("BodySound") or Instance.new("Sound")
body.Name = "BodySound"
body.RollOffMaxDistance = 100
body.RollOffMinDistance = 10
body.RollOffMode = Enum.RollOffMode.InverseTapered
body.Looped = true
body.SoundId = "rbxassetid://4471836491"
body.Volume = 0
body.Parent = weight

local dragPart = UseWeight and weight or Directory.Drag
local dfPart = UseWeight and weight or Directory.DownforceF
local drPart = UseWeight and weight or Directory.DownforceR

local dragAttach = dragPart:FindFirstChild("DragAttachment") or Instance.new("Attachment", dragPart)
dragAttach.Name = "DragAttachment"
local dfAttach = dfPart:FindFirstChild("DownforceFAttachment") or Instance.new("Attachment", dfPart)
dfAttach.Name = "DownforceFAttachment"
dfAttach.Position = Vector3.new(0, 0, UseWeight and -weight.Size.Z/2 or 0)
local drAttach = drPart:FindFirstChild("DownforceRAttachment") or Instance.new("Attachment", drPart)
drAttach.Name = "DownforceRAttachment"
drAttach.Position = Vector3.new(0, 0, UseWeight and weight.Size.Z/2 or 0)
local dragForce = dragPart:FindFirstChild("DragForce") or Instance.new("VectorForce", dragPart)
dragForce.Name = "DragForce"
dragForce.Attachment0 = dragAttach
dragForce.RelativeTo = Enum.ActuatorRelativeTo.World
local dfForce = dfPart:FindFirstChild("DownforceFForce") or Instance.new("VectorForce", dfPart)
dfForce.Name = "DownforceFForce"
dfForce.Attachment0 = dfAttach
dfForce.RelativeTo = Enum.ActuatorRelativeTo.World
local drForce = drPart:FindFirstChild("DownforceRForce") or Instance.new("VectorForce", drPart)
drForce.Name = "DownforceRForce"
drForce.Attachment0 = drAttach
drForce.RelativeTo = Enum.ActuatorRelativeTo.World

wind:Play()
body:Play()

-- Create our raycast parameters for slipstream.
local aeroParams = RaycastParams.new()
aeroParams.FilterDescendantsInstances = {car, player.Character}
aeroParams.FilterType = Enum.RaycastFilterType.Exclude
local aeroParamsFrom = RaycastParams.new()
aeroParamsFrom.FilterDescendantsInstances = {}
aeroParamsFrom.FilterType = Enum.RaycastFilterType.Exclude

task.wait()
for _, v in next, car:GetDescendants() do
	if v:IsA("BasePart") then
		v.EnableFluidForces = false
	end
end

-- On player leave
car.DriveSeat.ChildRemoved:Connect(function(child)
	if child.Name=="SeatWeld" and child:IsA("Weld") then
		wind:Destroy()
		body:Destroy()
		dragForce.Force = Vector3.new()
		dfForce.Force = Vector3.new()
		drForce.Force = Vector3.new()
		script:Destroy()
	end
end)

-- Loop
while task.wait(SimulationRate or 1/30) do
	if (VelocityValue.Value-workspace.GlobalWind).Magnitude < 1 then continue end

	local AirPressurePercent = math.exp((-9.80665 * 0.0289644 * (CenterValue.Value.Position.Y * Units.Length_mm / 1000)) / (8.3142 * ((workspace:GetAttribute("GlobalTemperature") or 22) + 273.15))) * (workspace.AirDensity/0.001)

	-- Slipstream
	_DragFactor = 1
	if Slipstream then
		local toRay = workspace:Raycast(weight.Position, weight.CFrame.LookVector*1000, aeroParams)
		if toRay then
			aeroParamsFrom.FilterDescendantsInstances = toRay.Instance:GetConnectedParts()
			local fromRay = workspace:Raycast(toRay.Position, -toRay.Instance.Velocity*SlipstreamTime, aeroParamsFrom)
			if fromRay and fromRay.Instance:IsDescendantOf(car) then
				_DragFactor = (1-(Slipstream/100))+(fromRay.Distance/(toRay.Instance.Velocity*SlipstreamTime).Magnitude*(Slipstream/100))
			end
		end
	end

	-- Apply forces
	local carVel = VelocityValue.Value * toKMH
	local windVel = workspace.GlobalWind * toKMH
	local diff = (carVel - windVel) * toKMH
	local objectVel = weight.CFrame:VectorToObjectSpace(carVel-windVel)

	local df = math.sign(objectVel.Z)*(objectVel.Z^2/(200)^2)*(DownforceF)*_DragFactor
	local dr = math.sign(objectVel.Z)*(objectVel.Z^2/(200)^2)*(DownforceR)*_DragFactor
	local combinedForce = -diff.Unit*diff.Magnitude^2*((Drag*DragMult)/100) * AirPressurePercent

	dfForce.Force = weight.CFrame.UpVector*df*fromKGF
	drForce.Force = weight.CFrame.UpVector*dr*fromKGF
	dragForce.Force = combinedForce*fromKGF

	-- Apply sounds
	wind.Volume = diff.Magnitude^2 * WindVolume / (200)^2
	body.Volume = diff.Magnitude^2 * BodyVolume / (200)^2
end


--[[
		             _____ _                   _
	     /\         / ____| |                 (_)
	    /  \ ______| |    | |__   __ _ ___ ___ _ ___
	   / /\ \______| |    | '_ \ / _` / __/ __| / __|
	  / ____ \     | |____| | | | (_| \__ \__ \ \__ \
	 /_/    \_\     \_____|_| |_|\__,_|___/___/_|___/
	 Version 1.7
--]]
-- rewritten by Detomiks
local UserInputService = game:GetService("UserInputService")
local playermouse = game:GetService("Players").LocalPlayer:GetMouse()

local car = script.Parent.Parent.Car.Value
local Controls = script.Parent.Parent:WaitForChild("Controls")
local ControlsOpen = script.Parent.Parent.ControlsOpen
local _Tune = require(car["A-Chassis Tune"])

local window = script.Parent.Window
local open = false

local allEntries = {}

local con1, con2 = nil, nil
local canInput, inputType, inputKey = false, nil, nil

local d, s, t = Enum.EasingDirection.Out, Enum.EasingStyle.Quart, .5

UserInputService.InputBegan:Connect(function(input, gameProcessedEvent)
	if not gameProcessedEvent then
		if input.KeyCode == Enum.KeyCode.V or input.KeyCode == Enum.KeyCode.ButtonL3 then
			script.Parent.Visible = not script.Parent.Visible
		end
	end
end)

--setup
local keyboard = window.KeyboardSF
local mouse = keyboard:Clone() mouse.Name = "MouseSF" mouse.Position = UDim2.new(1, 5, 0, 80) mouse.Parent = window
local contlr = keyboard:Clone() contlr.Name = "ControllerSF" contlr.Position = UDim2.new(2, 5, 0, 80) contlr.Parent = window

for name, keycode in pairs(_Tune.Controls) do
	local entry = keyboard.Placeholder:Clone()
	entry.Name = name
	entry.TextButton.Text = keycode.Name
	entry.Visible = true

	if string.find(name, "Mouse") then
		entry.Text = string.gsub(name, "Mouse", "")
		entry.Parent = mouse
	elseif string.find(name, "Contlr") then
		entry.Text = string.gsub(name, "Contlr", "")
		entry.Parent = contlr
	else
		entry.Text = name
		entry.Parent = keyboard
	end
	table.insert(allEntries, entry)
end

for name, value in pairs(_Tune.Peripherals) do
	local entry = keyboard.PlaceholderPeripheral:Clone()
	entry.Name = name
	entry.Frame.TextButton.Position = UDim2.new(value/100, 0, .5, 0)
	entry.Frame.TextButton.TextBox.Text = math.round(value) .."%"
	entry.Visible = true

	if string.find(name, "M") then
		entry.Text = string.gsub(name, "M", "")
		entry.Parent = mouse
	elseif string.find(name, "Control") then
		entry.Text = string.gsub(name, "Control", "")
		entry.Parent = contlr
	else
		entry.Text = name
		entry.Parent = keyboard
	end
	table.insert(allEntries, entry)
end

--navigation
local function navTo(button)
	for _, v in next, window:GetChildren() do
		if v:IsA("TextButton") then
			if v.Name == button.Name then
				v.BackgroundColor3 = Color3.new(1,1,1)
				v.BackgroundTransparency = 0
				v.TextColor3 = Color3.new()
				v.UIGradient.Enabled = false
				v.Frame.Visible = true
			else
				v.BackgroundColor3 = Color3.new()
				v.BackgroundTransparency = .5
				v.TextColor3 = Color3.new(1,1,1)
				v.UIGradient.Enabled = true
				v.Frame.Visible = false
			end
		end
	end
	window.Highlight:TweenPosition(UDim2.new(button.Position.X.Scale, 0, 0, 77), d, s, t, true, nil)
	local pos = 0
	if button.Name == "Controller" then
		pos = -2
	elseif button.Name == "Mouse" then
		pos = -1
	end
	keyboard:TweenPosition(UDim2.new(pos, 5, 0, 80), d, s, t, true, nil)
	mouse:TweenPosition(UDim2.new(pos + 1, 5, 0, 80), d, s, t, true, nil)
	contlr:TweenPosition(UDim2.new(pos + 2, 5, 0, 80), d, s, t, true, nil)
end
window.Controller.MouseButton1Click:Connect(function()
	navTo(window.Controller)
end)
window.Mouse.MouseButton1Click:Connect(function()
	navTo(window.Mouse)
end)
window.Keyboard.MouseButton1Click:Connect(function()
	navTo(window.Keyboard)
end)

--key assigning
for _, entry in next, allEntries do
	local button = entry:FindFirstChildWhichIsA("TextButton")
	if button then --this is a normal key press thing
		button.MouseButton1Click:Connect(function()
			if con1 then con1:Disconnect() end
			if con2 then con2:Disconnect() end
			ControlsOpen.Value = true
			window.Overlay.Visible = true
			canInput = true

			con1 = UserInputService.InputBegan:Connect(function(input)
				if canInput then
					inputKey, inputType = input.KeyCode, input.UserInputType
				end
			end)
			con2 = UserInputService.InputChanged:Connect(function(input)
				if canInput and (input.KeyCode==Enum.KeyCode.Thumbstick1 or input.KeyCode==Enum.KeyCode.Thumbstick2) then
					inputKey, inputType = input.KeyCode, input.UserInputType
				end
			end)

			repeat wait() until inputKey
			if con1 then con1:Disconnect() end
			if con2 then con2:Disconnect() end

			if inputKey == Enum.KeyCode.Return or inputKey == Enum.KeyCode.KeypadEnter then
				-- do nothing
			elseif string.find(entry.Name, "Contlr") then
				if string.find(inputType.Name, "Gamepad") then
					Controls[entry.Name].Value = inputKey.Name
					entry.TextButton.Text = inputKey.Name
				else
					window.Error.Visible = true
				end
			elseif entry.Name == "MouseThrottle" or entry.Name == "MouseBrake" then
				if inputType == Enum.UserInputType.MouseButton1 or inputType == Enum.UserInputType.MouseButton2 then
					Controls[entry.Name].Value = inputType.Name
					button.Text = inputType.Name
				elseif inputType ==  Enum.UserInputType.Keyboard then
					Controls[entry.Name].Value = inputKey.Name
					button.Text = inputKey.Name
				else
					window.Error.Visible = true
				end
			else
				if inputType == Enum.UserInputType.Keyboard then
					Controls[entry.Name].Value = inputKey.Name
					button.Text = inputKey.Name
				else
					window.Error.Visible = true
				end
			end

			inputKey, inputType, canInput = nil, nil, false
			wait(.2)
			window.Overlay.Visible = false
			ControlsOpen.Value = false
		end)
	else --this is a slider
		local holding = false
		entry.Frame.TextButton.MouseButton1Down:Connect(function()
			holding = true
		end)
		UserInputService.InputEnded:Connect(function(input)
			if input.UserInputType == Enum.UserInputType.MouseButton1 then
				holding = false
			end
		end)
		playermouse.Move:Connect(function()
			if holding then
				local x = (playermouse.X-entry.Frame.AbsolutePosition.X)/entry.Frame.AbsoluteSize.X
				entry.Frame.TextButton.Position = UDim2.new(math.clamp(x, 0, 1), 0, .5, 0)
			end
		end)
		entry.Frame.TextButton.Changed:Connect(function()
			Controls[entry.Name].Value = entry.Frame.TextButton.Position.X.Scale * 100
			entry.Frame.TextButton.TextBox.Text = math.round(Controls[entry.Name].Value).."%"
		end)
		entry.Frame.TextButton.TextBox:GetPropertyChangedSignal("CursorPosition"):Connect(function()
			if entry.Frame.TextButton.TextBox.CursorPosition == -1 then
				entry.Frame.TextButton.Position = UDim2.new(math.clamp((tonumber(entry.Frame.TextButton.TextBox.Text) or Controls[entry.Name].Value)/100,0,1), 0, .5, 0)
			end
		end)
	end
end

window.Error:GetPropertyChangedSignal("Visible"):Connect(function()
	wait(3)
	window.Error.Visible = false
end)

script.Parent.Toggle.MouseButton1Click:Connect(function()
	if open then
		script.Parent.Toggle.BackgroundColor3 = Color3.new(0,0,0)
		script.Parent.Toggle.BackgroundTransparency = .5
		script.Parent.Toggle.UIGradient.Enabled = true
		script.Parent.Toggle.TextColor3 = Color3.new(1,1,1)
		window:TweenPosition(UDim2.new(.5, 0, 1.5, 0), d, s, t, true, nil)
		open = false
	else
		script.Parent.Toggle.BackgroundColor3 = Color3.new(1,1,1)
		script.Parent.Toggle.BackgroundTransparency = 0
		script.Parent.Toggle.UIGradient.Enabled = false
		script.Parent.Toggle.TextColor3 = Color3.new(0,0,0)
		window:TweenPosition(UDim2.new(.5, 0, .5, 0), d, s, t, true, nil)
		open = true
	end
end)


--[[
		             _____ _                   _
	     /\         / ____| |                 (_)
	    /  \ ______| |    | |__   __ _ ___ ___ _ ___
	   / /\ \______| |    | '_ \ / _` / __/ __| / __|
	  / ____ \     | |____| | | | (_| \__ \__ \ \__ \
	 /_/    \_\     \_____|_| |_|\__,_|___/___/_|___/
	 Version 1.7
--]]
-- By detocoyote

-- Editable values
local LineLifetime = 1
local FPS = 15
local PollingRate = 60
local AntiJitter = 8

-- The rest of the script

-- Values
local car = script.Parent.Parent.Car.Value
local values = script.Parent.Parent.Values

-- Services
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local Debris = game:GetService("Debris")

local Units = require(car["A-Chassis Tune"].README.Units)
local _Tune = require(car["A-Chassis Tune"])

local gauge = script.Parent.Frame
local AccelerationValue = values.Acceleration
local history = {}
local framePool = {}

-- Visibility toggles
UserInputService.InputBegan:Connect(function(input, gameProcessedEvent)
	if not gameProcessedEvent then
		if input.KeyCode == Enum.KeyCode.V or input.KeyCode == Enum.KeyCode.ButtonL3 then
			script.Parent.Visible = not script.Parent.Visible
		end
	end
end)

gauge.ToggleNum.MouseButton1Click:Connect(function()
	gauge.XNum.Visible = not gauge.XNum.Visible
	gauge.YNum.Visible = not gauge.YNum.Visible
end)

-- Runtime loop
local function removeFrame(frame)
	frame.Parent = nil
	table.insert(framePool, frame)
end

local function PollingLoop()
	while task.wait(1/PollingRate) do
		local value = AccelerationValue.Value

		-- Values
		local accel = value*Vector3.new(1, 0, 1)/(Units.Velocity_mdivs)/((_Tune.GravComp>0 and _Tune.GravComp or workspace.Gravity)*(Units.Length_mm/1000))
		local g = -(car.Body["#Weight"].CFrame:VectorToObjectSpace(accel))

		-- GForce damping and setting things
		if g.Unit ~= g.Unit then g = Vector3.new(0.001, 0, 0) end -- Prevent 0, 0, 0

		table.insert(history, g.Magnitude * g.Unit)
		if #history > AntiJitter then
			table.remove(history, 1)
		end
	end
end

local function UpdateLoop()
	while task.wait(1/FPS) do
		local avgPos = Vector3.new()
		for _, pos in history do
			avgPos += pos
		end
		avgPos /= #history

		-- Clamping only, use avgPos for the actual values
		local old = gauge.Indicator.Position
		local indicatorPosition = avgPos.Unit * math.clamp(avgPos.Magnitude, 0, 3)
		gauge.Indicator.Position = UDim2.new(math.clamp(0.5 + 0.5*(indicatorPosition.X/3), -1, 1), 0, math.clamp(0.5 + 0.5*(indicatorPosition.Z/3), -1, 1), 0)

		-- Set discrete numbers
		gauge.XNum.Text = string.format("%.1f", math.round(avgPos.X * 10)/10)
		gauge.XNum.Text = gauge.XNum.Text == "-0.0" and "0.0" or gauge.XNum.Text
		gauge.YNum.Text = string.format("%.1f", math.round(avgPos.Z * 10)/10)
		gauge.YNum.Text = gauge.YNum.Text == "-0.0" and "0.0" or gauge.YNum.Text

		-- Frame stuff
		local vec = Vector2.new(((gauge.Indicator.Position-old)).X.Scale, (gauge.Indicator.Position-old).Y.Scale)
		local frame: Frame
		if framePool[1] then
			frame = framePool[1]
			table.remove(framePool, 1)
		else
			frame = Instance.new("Frame")
			frame.Name = "Visual"
			frame.AnchorPoint = Vector2.new(.5, .5)
			frame.BackgroundColor3 = Color3.new(1,1,1)
			frame.BackgroundTransparency = 0.8
			frame.BorderSizePixel = 0
		end
		frame.Size = UDim2.new(vec.Magnitude, 0, .01, 0)
		frame.Rotation = math.atan(vec.Unit.Y/vec.Unit.X)*(180/math.pi)
		frame.Position = old:Lerp(gauge.Indicator.Position, .5)
		frame.Parent = gauge.Visuals

		-- Removal
		task.delay(LineLifetime, removeFrame, frame)
	end
end

task.spawn(PollingLoop)
task.spawn(UpdateLoop)


--[[
		             _____ _                   _
	     /\         / ____| |                 (_)
	    /  \ ______| |    | |__   __ _ ___ ___ _ ___
	   / /\ \______| |    | '_ \ / _` / __/ __| / __|
	  / ____ \     | |____| | | | (_| \__ \__ \ \__ \
	 /_/    \_\     \_____|_| |_|\__,_|___/___/_|___/
	 Version 1.7
--]]
-- Press "V" to cycle through gauges

local autoscaling	= true		--Estimates top speed
local Units = require(script.Parent.Parent.Car.Value["A-Chassis Tune"].README.Units)

local StartupAnimation		= true
local MaxFPS = 240

local UNITS	= {								--Click on speed to change units
	--First unit is default
	{
		units			= "mph"				,
		scaling			= (Units.Length_mm/1000) * 2.23693629	, -- 1 stud : 280 mm | m/s to mi/h
		maxSpeed		= 180				,
		spInc			= 20				, -- Increment between labelled notches
	},

	{
		units			= "km/h"			,
		scaling			= (Units.Length_mm/1000) * 3.6	, -- 1 stud : 280 mm | m/s to km/h
		maxSpeed		= 280				,
		spInc			= 30				, -- Increment between labelled notches
	},

	{
		units			= "sps"				,
		scaling			= 1					, -- Roblox standard
		maxSpeed		= 280				,
		spInc			= 30				, -- Increment between labelled notches
	}
}

-----------------------------------------------------------------------------------------------

local car = script.Parent.Parent.Car.Value
local values = script.Parent.Parent.Values
local _Tune = require(car:WaitForChild("A-Chassis Tune"))
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local TweenService = game:GetService("TweenService")

local VirtualInput: BindableEvent = script.Parent.Parent.Drive.VirtualInput
local Controls = script.Parent.Parent:WaitForChild("Controls")

UserInputService.InputBegan:Connect(function(input, gameProcessedEvent)
	if not gameProcessedEvent then
		if input.KeyCode == Enum.KeyCode.V or input.KeyCode == Enum.KeyCode.ButtonL3 then
			script.Parent.Visible = not script.Parent.Visible
		end
	end
end)

local gauges = script.Parent.Advanced --use this if you want to add gauge modes later on
local currentUnit = 1

local _TCount = _Tune.Turbochargers
local _SCount = _Tune.Superchargers
local tboost = 0
local sboost = 0

local tachRot = 0
local speedoRot = 0
local boostRot = 0

local redlineLight = 0
local rpmHistory = {}

local bold = Font.new("rbxasset://fonts/families/SourceSansPro.json", Enum.FontWeight.SemiBold, Enum.FontStyle.Normal)
local boldItalic = Font.new("rbxasset://fonts/families/SourceSansPro.json", Enum.FontWeight.SemiBold, Enum.FontStyle.Italic)

local starting = false
local startingPressed = false
local startAnimAlpha = 0 -- do it like this, since when we set it back to the original position, tweens make it jump to the rotation instead of smoothly
local startAnimStart = tick()
local startAnimTime = 0
local startAnimLength = .5
local startOldAlpha = 0

-- Value Caching
local RPMValue = values.RPM
local GearValue = values.Gear
local VelocityValue = values.Velocity
local TCSValue = values.TCS
local TCSActiveValue = values.TCSActive
local BoostTurboValue = values.BoostTurbo
local BoostSuperValue = values.BoostSuper
local ClutchValue = values.Clutch
local TransmissionModeValue = values.TransmissionMode
local ABSValue = values.ABS
local ABSActiveValue = values.ABSActive
local ESCValue = values.ESC
local ESCActiveValue = values.ESCActive
local CSValue = values.CS
local CSActiveValue = values.CSActive
local PBrakeValue = values.PBrake

-- Object Caching
local TachNeedle = gauges.Tach.Needle
local SpeedoNeedle = gauges.Speedo.Needle
local SpeedText = gauges.Speed
local UnitText = gauges.Unit
local GearTextLabel = gauges.Gear
local TModeTextLabel = gauges.TMode
local BoostNeedle = gauges.Boost.Needle
local PSIText = gauges.PSI
local TCSImage = gauges.TCS
local TCSAlt = TCSImage.Alt
local ABSImage = gauges.ABS
local ABSAlt = ABSImage.Alt
local ESCImage = gauges.ESC
local ESCAlt = ESCImage.Alt
local CSImage = gauges.CS
local CSAlt = CSImage.Alt
local PBrakeImage = gauges.PBrake
local PBrakeAlt = PBrakeImage.Alt
local RedlineLight = gauges.RedlineLight

-- Initialize
car.DriveSeat.HeadsUpDisplay = false

if _Tune.Electric and not _Tune.Engine then
	_Tune.Redline = _Tune.E_Redline
	_Tune.ShiftRPM = _Tune.Redline
	_Tune.Turbochargers = 0
	_Tune.Superchargers = 0
end

--Automatic Gauge Scaling
if autoscaling then
	local Drive={}
	if _Tune.Config == "FWD" or _Tune.Config == "AWD" then
		if car.Wheels:FindFirstChild("FL")~= nil then
			table.insert(Drive,car.Wheels.FL)
		end
		if car.Wheels:FindFirstChild("FR")~= nil then
			table.insert(Drive,car.Wheels.FR)
		end
		if car.Wheels:FindFirstChild("F")~= nil then
			table.insert(Drive,car.Wheels.F)
		end
	end
	if _Tune.Config == "RWD" or _Tune.Config == "AWD" then
		if car.Wheels:FindFirstChild("RL")~= nil then
			table.insert(Drive,car.Wheels.RL)
		end
		if car.Wheels:FindFirstChild("RR")~= nil then
			table.insert(Drive,car.Wheels.RR)
		end
		if car.Wheels:FindFirstChild("R")~= nil then
			table.insert(Drive,car.Wheels.R)
		end
	end

	local wDia = 0
	for i,v in pairs(Drive) do
		if v.Size.Y>wDia then wDia = v.Size.Y end
	end
	Drive = nil
	for i,v in pairs(UNITS) do
		v.maxSpeed = math.ceil(v.scaling*(_Tune.SpeedLimit > 0 and _Tune.SpeedLimit/((Units.Length_mm/1000)*3.6) or wDia*math.pi*_Tune.Redline/60/(_Tune.ClutchType == "CVT" and _Tune.CVTMinRatio or _Tune.Ratios[#_Tune.Ratios])/_Tune.FinalDrive/_Tune.FDMult))
		v.spInc = math.max(math.ceil(v.maxSpeed/100)*10,10)
	end
end

-- Tach
local revScale = math.ceil(_Tune.Redline/12000) * 250
local revEnd = (math.ceil(_Tune.Redline/1000)*1000)/revScale
for i = 0, revEnd do
	local newln = gauges.ln:Clone()
	newln.Parent = gauges.TachBackground
	newln.Rotation = -135 + 225*(i/revEnd)
	newln.Num.Text = (i * revScale)/1000
	newln.Num.Rotation = -newln.Rotation
	if i%4 ~= 0 then
		newln.Num:Destroy()
	end
	if (i+1)%2 == 0 then
		newln.Frame.Size = UDim2.new(.005, 0, .02, 0)
	elseif (i+2)%4 == 0 then
		newln.Frame.Size = UDim2.new(.01, 0, .05, 0)
	end
	newln.Visible = true
end
local redlineRotation = ((1-_Tune.ShiftRPM/(revEnd*revScale)) * 225)
gauges.TachBackground.Redline.High.ImageLabel.Rotation = math.clamp(180-redlineRotation, 0, 180)
gauges.TachBackground.Redline.Low.ImageLabel.Rotation = math.clamp(180-redlineRotation, -45, 0)
gauges.TachBackground.Redline.High.ImageLabel.Visible = redlineRotation > 0
gauges.TachBackground.Redline.Low.ImageLabel.Visible = redlineRotation > 180
gauges.TachBackground.Redline.Alt.Rotation = 180-redlineRotation

-- Speedometer
for i, v in next, UNITS do
	local lnn = Instance.new("Frame",gauges.SpeedoBackground)
	lnn.BackgroundTransparency = 1
	lnn.BorderSizePixel = 0
	lnn.Size = UDim2.new(1,0,1,0)
	lnn.AnchorPoint = Vector2.new(.5,.5)
	lnn.Position = UDim2.new(.5, 0, .5, 0)
	lnn.Name = v.units
	if i ~= 1 then lnn.Visible = false end

	for i = 0, v.maxSpeed, v.spInc/4 do
		local ln = gauges.ln:Clone()
		ln.Parent = lnn
		ln.Rotation = -135 + 225*(i/v.maxSpeed)
		ln.Num.Text = i
		ln.Num.Size = UDim2.new(.2, 0, .07, 0)
		ln.Num.Position = UDim2.new(.5, 0, .06, 0)
		ln.Num.Rotation = -ln.Rotation
		if i%v.spInc ~= 0 then
			ln.Num:Destroy()
			if i%(v.spInc/2) == 0 then
				ln.Frame.Size = UDim2.new(.01, 0, .05, 0)
			end
		end
		ln.Visible=true
	end
end

if _TCount~=0 or _SCount~=0 then
	for i = 0, 12 do
		local newbln = gauges.bln:Clone()
		newbln.Parent = gauges.BoostBackground
		newbln.Rotation = -180 + 270*(i/12)
		newbln.Visible = true
		if i%4 ~= 0 then
			newbln.Frame.Size = UDim2.new(.03, 0, .05, 0)
		end
		newbln.Num.Text = "0"
		newbln.Num.Rotation = -newbln.Rotation
		if i ~= 4 then
			newbln.Num:Destroy()
		end
	end
else
	gauges.Boost.Visible = false
	gauges.PSI.Visible = false
	gauges.BoostBackground.Visible = false
	gauges.PSIUnit.Visible = false
end

--Unit switcher
SpeedText.MouseButton1Click:Connect(function()
	currentUnit += 1
	if currentUnit > #UNITS then
		currentUnit = 1
	end
	for _, v in next, gauges.SpeedoBackground:GetChildren() do
		if v:IsA("Frame") then
			v.Visible = false
		end
	end
	gauges.SpeedoBackground[UNITS[currentUnit].units].Visible = true
end)

--Auto Clutch Toggle
GearTextLabel.MouseButton1Click:Connect(function()
	values.AutoClutch.Value = not values.AutoClutch.Value
	if values.AutoClutch.Value then
		GearTextLabel.FontFace = bold
	else
		GearTextLabel.FontFace = boldItalic
	end
end)

-- Enable gauge light visibility
gauges.TCS.Visible = _Tune.TCSEnabled
gauges.ABS.Visible = _Tune.ABSEnabled
gauges.ESC.Visible = _Tune.ESCEnabled and _Tune.TCSEnabled
gauges.CS.Visible = _Tune.CSEnabled
gauges.PBrake.Visible = _Tune.PBrakeForce > 0

if not _Tune.TCSEnabled then
	gauges.ABS.Position = gauges.TCS.Position
end
if not (_Tune.ESCEnabled and _Tune.TCSEnabled) then
	gauges.CS.Position = gauges.PBrake.Position
	gauges.PBrake.Position = gauges.ESC.Position
end

-- VirtalInput button toggles
gauges.ABS.Button.MouseButton1Click:Connect(function()
	VirtualInput:Fire{KeyCode = Enum.KeyCode[Controls.ToggleABS.Value], UserInputState = Enum.UserInputState.End}
end)

gauges.CS.Button.MouseButton1Click:Connect(function()
	VirtualInput:Fire{KeyCode = Enum.KeyCode[Controls.ToggleCS.Value], UserInputState = Enum.UserInputState.End}
end)

gauges.ESC.Button.MouseButton1Click:Connect(function()
	VirtualInput:Fire{KeyCode = Enum.KeyCode[Controls.ToggleTCS.Value], UserInputState = Enum.UserInputState.End}
end)

gauges.TCS.Button.MouseButton1Click:Connect(function()
	VirtualInput:Fire{KeyCode = Enum.KeyCode[Controls.ToggleTCS.Value], UserInputState = Enum.UserInputState.End}
end)

gauges.PBrake.Button.MouseButton1Down:Connect(function()
	VirtualInput:Fire{KeyCode = Enum.KeyCode[Controls.PBrake.Value], UserInputState = Enum.UserInputState.Begin}
end)

gauges.PBrake.Button.MouseButton1Up:Connect(function()
	VirtualInput:Fire{KeyCode = Enum.KeyCode[Controls.PBrake.Value], UserInputState = Enum.UserInputState.End}
end)

TModeTextLabel.Button.MouseButton1Click:Connect(function()
	VirtualInput:Fire{KeyCode = Enum.KeyCode[Controls.ToggleTransMode.Value], UserInputState = Enum.UserInputState.Begin}
end)

-- Warnings
local function updateTCS()
	if TCSValue.Value or starting then
		if TCSActiveValue.Value or starting then
			TCSImage.ImageColor3 = Color3.new(1,1,1)
			TCSImage.ImageTransparency = 0
		else
			TCSImage.ImageColor3 = Color3.new(0,0,0)
			TCSImage.ImageTransparency = .8
		end
	else
		if _Tune.TCSEnabled then
			TCSImage.ImageColor3 = Color3.new(1,0,0)
			TCSImage.ImageTransparency = 0
		else
			TCSImage.ImageColor3 = Color3.new(0,0,0)
			TCSImage.ImageTransparency = .8
		end
	end
	TCSAlt.TextColor3 = TCSImage.ImageColor3
	TCSAlt.TextTransparency = TCSImage.ImageTransparency
end

local function updateABS()
	if ABSValue.Value or starting then
		if ABSActiveValue.Value or starting then
			ABSImage.ImageColor3 = Color3.new(1,1,1)
			ABSImage.ImageTransparency = 0
		else
			ABSImage.ImageColor3 = Color3.new(0,0,0)
			ABSImage.ImageTransparency = .8
		end
	else
		if _Tune.ABSEnabled then
			ABSImage.ImageColor3 = Color3.new(1,0,0)
			ABSImage.ImageTransparency = 0
		else
			ABSImage.ImageColor3 = Color3.new(0,0,0)
			ABSImage.ImageTransparency = .8
		end
	end
	ABSAlt.TextColor3 = ABSImage.ImageColor3
	ABSAlt.TextTransparency = ABSImage.ImageTransparency
end

local function updateESC()
	if ESCValue.Value or starting then
		if ESCActiveValue.Value or starting then
			ESCImage.ImageColor3 = Color3.new(1,1,1)
			ESCImage.ImageTransparency = 0
		else
			ESCImage.ImageColor3 = Color3.new(0,0,0)
			ESCImage.ImageTransparency = .8
		end
	else
		if _Tune.ESCEnabled then
			ESCImage.ImageColor3 = Color3.new(1,0,0)
			ESCImage.ImageTransparency = 0
		else
			ESCImage.ImageColor3 = Color3.new(0,0,0)
			ESCImage.ImageTransparency = .8
		end
	end
	ESCAlt.TextColor3 = ESCImage.ImageColor3
	ESCAlt.TextTransparency = ESCImage.ImageTransparency
end

local function updateCS()
	if CSValue.Value or starting then
		if CSActiveValue.Value or starting then
			CSImage.ImageColor3 = Color3.new(1,.66,0)
			CSImage.ImageTransparency = 0
		else
			CSImage.ImageColor3 = Color3.new(0,0,0)
			CSImage.ImageTransparency = .8
		end
	else
		if _Tune.CSEnabled then
			CSImage.ImageColor3 = Color3.new(1,0,0)
			CSImage.ImageTransparency = 0
		else
			CSImage.ImageColor3 = Color3.new(0,0,0)
			CSImage.ImageTransparency = .8
		end
	end
	CSAlt.TextColor3 = CSImage.ImageColor3
	CSAlt.TextTransparency = CSImage.ImageTransparency
end

local function updatePBrake()
	if PBrakeValue.Value or starting then
		PBrakeImage.ImageColor3 = Color3.new(1,1,1)
		PBrakeImage.ImageTransparency = 0
	else
		PBrakeImage.ImageColor3 = Color3.new(0,0,0)
		PBrakeImage.ImageTransparency = .8
	end
	PBrakeAlt.TextColor3 = PBrakeImage.ImageColor3
	PBrakeAlt.TextTransparency = PBrakeImage.ImageTransparency
end

-- Events
TransmissionModeValue.Changed:Connect(function(tmodetext)
	if tmodetext == "Auto" then tmodetext = "A/T"
	elseif tmodetext == "Semi" then tmodetext = "S/T"
	elseif tmodetext == "Manual" then tmodetext = "M/T"
	end
	TModeTextLabel.Text = tmodetext
end)

GearValue.Changed:Connect(function(gearText)
	if gearText == -2 then gearText = "P"
	elseif gearText == -1 then gearText = "R"
	elseif gearText == 0 then gearText = "N"
	elseif gearText == 1 and _Tune.ClutchType == "CVT" then gearText = "D"
	end
	GearTextLabel.Text = gearText
end)

ClutchValue.Changed:Connect(function(value)
	GearTextLabel.TextTransparency = (value > .9 and starting == false) and .8 or 0
end)

TCSValue.Changed:Connect(updateTCS)
TCSActiveValue.Changed:Connect(updateTCS)

ABSValue.Changed:Connect(updateABS)
ABSActiveValue.Changed:Connect(updateABS)

ESCValue.Changed:Connect(updateESC)
ESCActiveValue.Changed:Connect(updateESC)

CSValue.Changed:Connect(updateCS)
CSActiveValue.Changed:Connect(updateCS)

PBrakeValue.Changed:Connect(updatePBrake)

-- Startup Sweep
if StartupAnimation then
	local startupInfo = TweenInfo.new(.5, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut, 0, false, 0)
	script.Parent.Parent.IsOn.Starting.Changed:Connect(function()
		if script.Parent.Parent.IsOn.Starting.Value then
			starting = true
			startingPressed = true
			startAnimStart = tick()
		else
			startingPressed = false
			startAnimStart = tick()
			startOldAlpha = startAnimAlpha
		end
		updateTCS()
		updateABS()
		updateESC()
		updateCS()
		updatePBrake()
	end)
end

local function AltImageHandlerLoop()
	while task.wait() do
		gauges.TachBackground.Redline.Alt.Visible = not gauges.TachBackground.Redline.High.ImageLabel.IsLoaded
		TachNeedle.Alt.Visible = not TachNeedle.ImageLabel.IsLoaded
		SpeedoNeedle.Alt.Visible = not SpeedoNeedle.ImageLabel.IsLoaded
		BoostNeedle.Alt.Visible = not BoostNeedle.ImageLabel.IsLoaded
		TCSAlt.Visible = not TCSImage.IsLoaded
		ABSAlt.Visible = not ABSImage.IsLoaded
		ESCAlt.Visible = not ESCImage.IsLoaded
		CSAlt.Visible = not CSImage.IsLoaded
		PBrakeAlt.Visible = not PBrakeImage.IsLoaded
		if gauges.TachBackground.Redline.High.ImageLabel.IsLoaded and TachNeedle.ImageLabel.IsLoaded and BoostNeedle.ImageLabel.IsLoaded and TCSImage.IsLoaded and ABSImage.IsLoaded and ESCImage.IsLoaded and CSImage.IsLoaded and PBrakeImage.IsLoaded then
			return -- return after all the images are loaded
		end
	end
end
task.spawn(AltImageHandlerLoop)

-- Runtime loop
local redlineIndicatorTick = tick()
local last = tick()
local function RuntimeLoop()
	if tick()-last < 1/MaxFPS then return end
	last = tick()
	if not script.Parent.Visible then return end
	--Starting animation values
	if StartupAnimation then
		startAnimTime = math.clamp((tick()-startAnimStart)/startAnimLength, 0, 1)
		local startAnimEquation = (math.sin(math.pi * (startAnimTime - 0.5)) + 1)/2
		if startingPressed then
			startAnimAlpha = startAnimEquation
		else
			startAnimAlpha = (1-(startAnimEquation))*startOldAlpha
			if startAnimTime == 1 then
				if starting then
					starting = false
					updateTCS()
					updateABS()
					updateESC()
					updateCS()
					updatePBrake()
				end
			end
		end
	end

	--RPM
	tachRot = 225 * ((RPMValue.Value)/(revEnd*revScale))
	TachNeedle.Rotation = math.max(tachRot + (225-tachRot) * startAnimAlpha, 0)

	--Speed
	speedoRot = 225 * ((VelocityValue.Value.Magnitude*UNITS[currentUnit].scaling)/UNITS[currentUnit].maxSpeed)
	SpeedoNeedle.Rotation = math.max(speedoRot + (225-speedoRot) * startAnimAlpha, 0)
	SpeedText.Text = math.round(VelocityValue.Value.Magnitude*UNITS[currentUnit].scaling)
	UnitText.Text = UNITS[currentUnit].units

	--Boost
	local _T = 0
	local _S = 0
	if _TCount~=0 then _T = _Tune.T_Boost*_TCount end
	if _SCount~=0 then _S = _Tune.S_Boost*_SCount end
	tboost = (BoostTurboValue.Value*1.2)-(_T/5)
	sboost = BoostSuperValue.Value
	boostRot = math.clamp(90 + 180*math.min(1,(tboost+sboost)/(_T+_S)), 0, 270)
	BoostNeedle.Rotation = boostRot + (270-boostRot) * startAnimAlpha
	PSIText.Text = string.format("%.1f", tboost+sboost)

	-- MISC
	if tick()-redlineIndicatorTick > 1/60 then
		redlineIndicatorTick = tick()
		RedlineLight.BackgroundColor3 = Color3.new(.15, .15, .15)
		if RPMValue.Value > _Tune.ShiftRPM then
			redlineLight += 1
			if redlineLight <= 5 then
				RedlineLight.BackgroundColor3 = Color3.new(1, 0, 0)
			end
			if redlineLight == 10 then
				redlineLight = 0
			end
		end
	end
	if starting then
		RedlineLight.BackgroundColor3 = Color3.new(1, 0, 0)
	end
end

RunService.Heartbeat:Connect(RuntimeLoop)


--[[
		             _____ _                   _
	     /\         / ____| |                 (_)
	    /  \ ______| |    | |__   __ _ ___ ___ _ ___
	   / /\ \______| |    | '_ \ / _` / __/ __| / __|
	  / ____ \     | |____| | | | (_| \__ \__ \ \__ \
	 /_/    \_\     \_____|_| |_|\__,_|___/___/_|___/
	 Version 1.7
--]]
-- By Detomiks
--------------------------------------------------------------------------

local IGNITION_KEYBOARD = Enum.KeyCode.F
local IGNITION_CONTROLLER = Enum.KeyCode.ButtonB

local UserInputService = game:GetService("UserInputService")
local gamepadPresent = UserInputService.GamepadEnabled

local function onInput(input:InputObject, gameProcessedEvent:boolean)
	if gameProcessedEvent then return end
	if not (input.KeyCode == IGNITION_KEYBOARD or input.KeyCode == IGNITION_CONTROLLER) then return end
	if script.Parent.Parent.IsOn.Value and not script.Parent.Parent.IsOn.Starting.Value then
		if input.UserInputState == Enum.UserInputState.End then return end
		script.Parent.Parent.IsOn.Value = false
	else
		script.Parent.Parent.IsOn.Starting.Value = input.UserInputState == Enum.UserInputState.Begin
	end
end

local function update()
	gamepadPresent = UserInputService.GamepadEnabled
	script.Parent.Text = "Hold <stroke color='#000000' thickness='2'>"..(gamepadPresent and IGNITION_CONTROLLER.Name or IGNITION_KEYBOARD.Name).."</stroke> to turn on."
	script.Parent.Visible = not script.Parent.Parent.IsOn.Value
end

UserInputService.InputBegan:Connect(onInput)
UserInputService.InputChanged:Connect(onInput)
UserInputService.InputEnded:Connect(onInput)

UserInputService.GamepadConnected:Connect(update)
UserInputService.GamepadDisconnected:Connect(update)
script.Parent.Parent.IsOn.Changed:Connect(update)
update()


--[[
		             _____ _                   _
	     /\         / ____| |                 (_)
	    /  \ ______| |    | |__   __ _ ___ ___ _ ___
	   / /\ \______| |    | '_ \ / _` / __/ __| / __|
	  / ____ \     | |____| | | | (_| \__ \__ \ \__ \
	 /_/    \_\     \_____|_| |_|\__,_|___/___/_|___/
	 Version 1.7
--]]
-- By vanilla01 and detocoyote

local GuiService = game:GetService("GuiService")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")

local player = Players.LocalPlayer
local mouse = player:GetMouse()

local camera = workspace.CurrentCamera
local frame = script.Parent

local car = frame.Parent.Car.Value :: Model
local values = frame.Parent.Values :: Folder
local seat = car.DriveSeat :: VehicleSeat

local valuesMouseSteerOn = values.MouseSteerOn :: BoolValue
local valuesVelocity = values.Velocity :: Vector3Value
local valuesCenter = values.Center :: CFrameValue

local RENDER_STEPPED_BIND_NAME = "MCam"
local RENDER_STEPPED_BIND_PRIORITY = Enum.RenderPriority.Camera.Value

-- Functions
local function onUpdate()
	local velocity = valuesVelocity.Value
	local center = valuesCenter.Value

	local pspeed = math.min(1, velocity.Magnitude / 500)
	local cc = center.Position + Vector3.new(0, 8 + (pspeed * 2), 0) - (center.LookVector * 17) + (velocity.Unit * -7 * pspeed)
	camera.CFrame = CFrame.lookAt(cc, center.Position)

	local centerX = camera.ViewportSize.X / 2
	local inset = GuiService:GetGuiInset() :: UDim2

	local height = mouse.Y + inset.Y
	local width = mouse.X - centerX

	frame.Position = UDim2.new(0, centerX, 0, height)
	frame.Size = UDim2.new(0, width, 0, 2)
end

local function onMouseSteerChanged()
	local value = valuesMouseSteerOn.Value
	frame.Visible = value

	if value then
		camera.CameraType = Enum.CameraType.Scriptable
		RunService:BindToRenderStep(RENDER_STEPPED_BIND_NAME, RENDER_STEPPED_BIND_PRIORITY, onUpdate)
	else
		camera.CameraType = Enum.CameraType.Custom
		RunService:UnbindFromRenderStep(RENDER_STEPPED_BIND_NAME)
	end
end

local function onChildRemoved(child: Instance)
	if child.Name == "SeatWeld" then
		camera.CameraType = Enum.CameraType.Custom
		RunService:UnbindFromRenderStep(RENDER_STEPPED_BIND_NAME)
	end
end

-- Connections
valuesMouseSteerOn:GetPropertyChangedSignal("Value"):Connect(onMouseSteerChanged)
seat.ChildRemoved:Connect(onChildRemoved)

-- Initialise
onMouseSteerChanged()


--[[
		             _____ _                   _
	     /\         / ____| |                 (_)
	    /  \ ______| |    | |__   __ _ ___ ___ _ ___
	   / /\ \______| |    | '_ \ / _` / __/ __| / __|
	  / ____ \     | |____| | | | (_| \__ \__ \ \__ \
	 /_/    \_\     \_____|_| |_|\__,_|___/___/_|___/
	 Version 1.7
--]]
--[[

	All the sounds are scalable, meaning that you can add or subtract as many sounds as you like.
	Each sound is characterized by a three-letter code in the beginning:
		- BOV is the blow-off valve
		- ENG is the engine
		- IGN is the ignition/turning over
		- SUP is the supercharger
		- TRA is the transmission
		- TUR is the turbocharger
	Each sound also includes a "SoundParent" attribute located at the bottom of the properties tab.
	The value of this attribute is the name of the part that the sound is parented to.
	You can change the location of said part in the value named "Directory" under this script.

	In the properties tab of each sound, there are also attributes at the bottom of the list.
	The engine sounds also include equalizer settings.
	Keep in mind the following settings for all sounds:
		- The "Volume" property does nothing, use the volume settings in the attributes section.
		- The "PlaybackSpeed" property also does nothing.

	The Sounds plugin script also includes FilteringEnabled (FE) settings.
	You can find more about those settings in the script.

]]

return


--[[
		             _____ _                   _
	     /\         / ____| |                 (_)
	    /  \ ______| |    | |__   __ _ ___ ___ _ ___
	   / /\ \______| |    | '_ \ / _` / __/ __| / __|
	  / ____ \     | |____| | | | (_| \__ \__ \ \__ \
	 /_/    \_\     \_____|_| |_|\__,_|___/___/_|___/
	 Version 1.7
--]]
-- *Most of the code documentation for here can be found in the LocalScript.

task.wait(.5)

local car = script.Parent.Parent
local event = script.Parent

local DIRECTORY = car.Body.Sounds

local _Tune = require(car["A-Chassis Tune"])
local Units = require(car["A-Chassis Tune"].README.Units)
local sounds = {}
local vvl = 0
local soundEQCache = {}
local modifierDataCache = {}
local canUpdate = false

local function cacheEQData(eq)
	if eq == nil then return end
	modifierDataCache[eq] = {
		HighGain = eq.HighGain,
		LowGain = eq.LowGain,
		MidGain = eq.MidGain,
	}
end
local function cacheDistortionData(dist)
	if dist == nil then return end
	modifierDataCache[dist] = dist.Level
end
local function create(children)
	for _, sound in children do
		if not sound:IsA("Sound") then continue end

		local newSound = sound:Clone()
		local offEQ = sound:FindFirstChild("OffEQ") or sound:FindFirstChild("ThrottleEQ") or sound:FindFirstChild("VVLEQ")
		local throttleEQ = sound:FindFirstChild("ThrottleEQ") or sound:FindFirstChild("OffEQ") or sound:FindFirstChild("VVLEQ")
		local vvlEQ = sound:FindFirstChild("VVLEQ") or sound:FindFirstChild("ThrottleEQ") or sound:FindFirstChild("OffEQ")
		local offDist = sound:FindFirstChild("OffDistortion") or sound:FindFirstChild("ThrottleDistortion")
		local throttleDist = sound:FindFirstChild("ThrottleDistortion") or sound:FindFirstChild("OffDistortion")
		soundEQCache[newSound] = {offEQ, throttleEQ, vvlEQ, offDist, throttleDist}

		cacheEQData(offEQ)
		cacheEQData(throttleEQ)
		cacheEQData(vvlEQ)
		cacheDistortionData(offDist)
		cacheDistortionData(throttleDist)

		newSound.Playing = false
		newSound.Volume = 0
		newSound.Parent = DIRECTORY and DIRECTORY:FindFirstChild(newSound:GetAttribute("SoundParent")) or car.Body["#Weight"]

		if newSound:FindFirstChild("OffDistortion") then newSound.OffDistortion.Enabled = false end
		if newSound:FindFirstChild("OffEQ") then newSound.OffEQ.Enabled = false end
		if newSound:FindFirstChild("ThrottleDistortion") then newSound.ThrottleDistortion.Enabled = false end
		if newSound:FindFirstChild("ThrottleEQ") then newSound.ThrottleEQ.Enabled = false end
		if newSound:FindFirstChild("VVLEQ") then newSound.VVLEQ.Enabled = false end
		local eq = Instance.new("EqualizerSoundEffect")
		eq.Name = "#EQ"
		eq.HighGain = 0
		eq.MidGain = 0
		eq.LowGain = 0
		eq.Parent = newSound
		local dist = Instance.new("DistortionSoundEffect")
		dist.Name = "#Distortion"
		dist.Level = 0
		dist.Parent = newSound

		local soundData = {
			Sound = newSound,
			type = string.sub(sound.Name, 1, 3),
			EQ = eq,
			Distortion = dist,
			Playing = false,
		}

		-- Cache all possible sound attributes
		soundData.BasePlaybackSpeed = sound:GetAttribute("BasePlaybackSpeed")
		soundData.BaseVolume = sound:GetAttribute("BaseVolume")
		soundData.RangeEnd = sound:GetAttribute("RangeEnd")
		soundData.RangeStart = sound:GetAttribute("RangeStart")
		soundData.RedlinePlaybackSpeed = sound:GetAttribute("RedlinePlaybackSpeed")
		soundData.RedlineVolume = sound:GetAttribute("RedlineVolume")
		soundData.ThrottleVolume = sound:GetAttribute("ThrottleVolume")
		soundData.TransitionEnd = sound:GetAttribute("TransitionEnd")
		soundData.TransitionStart = sound:GetAttribute("TransitionStart")
		soundData.ClutchVolume = sound:GetAttribute("ClutchVolume")
		soundData.Gears = string.split(string.gsub(soundData.Sound:GetAttribute("Gears") or "", " ", ""), ",")
		soundData.OffVolume = sound:GetAttribute("OffVolume")
		soundData.VariablePlaybackSpeed = sound:GetAttribute("VariablePlaybackSpeed")
		soundData.VariableVolume = sound:GetAttribute("VariableVolume")
		soundData.PlaybackSpeedIncrease = sound:GetAttribute("PlaybackSpeedIncrease")
		soundData.VolumeIncrease = sound:GetAttribute("VolumeIncrease")
		soundData.Sensitivity = sound:GetAttribute("Sensitivity")
		soundData.MaxPitch = sound:GetAttribute("MaxPitch")
		soundData.Pitch = sound:GetAttribute("Pitch")
		soundData.Volume = sound:GetAttribute("Volume")

		table.insert(sounds, soundData)
	end
end

local function f(m, n, p, q, M, b, x)
	return math.clamp(M - ( (q-n)/(b^p-b^m) * (b^x-b^m) + n ), 0, M)
end

local function eqGain(sound, gainType, throt)
	local offEQ = soundEQCache[sound][1]
	if not offEQ then return end
	local throttleEQ = soundEQCache[sound][2]
	local vvlEQ = soundEQCache[sound][3]
	local eq = modifierDataCache[offEQ][gainType] + ((modifierDataCache[throttleEQ][gainType] + (modifierDataCache[vvlEQ][gainType] - modifierDataCache[throttleEQ][gainType]) * vvl) - modifierDataCache[offEQ][gainType]) * throt
	return eq
end

local function distGain(sound, throt)
	local offDist = soundEQCache[sound][4]
	if not offDist then return end
	local throttleDist = soundEQCache[sound][5]
	return modifierDataCache[offDist] + (modifierDataCache[throttleDist] - modifierDataCache[offDist]) * throt
end

local updateBOV = false
local lastBOV = tick()
local prevBoost = 0
local lastLoop = tick()

local function update(player, packet)
	if not canUpdate then return end
	local RPM, Throttle, AverageRotSpeed, Gear, Clutch, BoostTurbo, BoostSuper, Starting, IsOn =
		buffer.readu16(packet, 0),
		buffer.readu8(packet, 2)/255,
		buffer.readu16(packet, 3),
		buffer.readi8(packet, 5),
		buffer.readu8(packet, 6)/255,
		buffer.readf32(packet, 7),
		buffer.readf32(packet, 11),
		buffer.readu8(packet, 15) == 1,
		buffer.readu8(packet, 16) == 1
	local dt = tick() - lastLoop
	if _Tune.CustomEngineCalculations == false or car["A-Chassis Tune"].README:FindFirstChild("CustomEngine") == nil then
		if _Tune.VVLTorque > 0 then
			vvl = (_Tune.VVLTorque - f(_Tune.VVLRPM/1000, 0, (_Tune.VVLRPM + _Tune.VVLTime)/1000, _Tune.VVLTorque, _Tune.VVLTorque, _Tune.VVLCurve, RPM/1000))/_Tune.VVLTorque
		else
			vvl = 0
		end
	end
	IsOn = IsOn and 1 or 0
	for _, soundData in sounds do
		if soundData.type == "ENG" then
			if soundData.Playing == false then
				soundData.Playing = true
				soundData.Sound.Playing = true
			end
			soundData.EQ.HighGain = eqGain(soundData.Sound, "HighGain", Throttle)
			soundData.EQ.MidGain = eqGain(soundData.Sound, "MidGain", Throttle)
			soundData.EQ.LowGain = eqGain(soundData.Sound, "LowGain", Throttle)
			soundData.Distortion.Level = distGain(soundData.Sound, Throttle)
			soundData.Sound.PlaybackSpeed = soundData.BasePlaybackSpeed + (soundData.RedlinePlaybackSpeed - soundData.BasePlaybackSpeed) * (RPM/_Tune.Redline)
			soundData.Sound.Volume = IsOn * (soundData.BaseVolume + (soundData.ThrottleVolume*Throttle) + (soundData.RedlineVolume*(RPM/_Tune.Redline))) * math.clamp(1/soundData.TransitionStart*(RPM-soundData.RangeStart),0,1) * math.clamp(-1/soundData.TransitionEnd*(RPM-soundData.RangeEnd),0,1)

		elseif soundData.type == "TRA" then
			if soundData.Playing == false then
				soundData.Playing = true
				soundData.Sound.Playing = true
			end
			soundData.Sound.PlaybackSpeed = soundData.VariablePlaybackSpeed * (AverageRotSpeed/(300*(Units.Length_mm/1000)*3.6))
			soundData.Sound.Volume = table.find(soundData.Gears, tostring(Gear)) and (soundData.ClutchVolume + ((soundData.BaseVolume + soundData.VariableVolume*(AverageRotSpeed/(300*(Units.Length_mm/1000)*3.6))) - soundData.ClutchVolume) * (1-Clutch) + soundData.ThrottleVolume*Throttle) or soundData.OffVolume

		elseif soundData.type == "TUR" then
			if soundData.Playing == false then
				soundData.Playing = true
				soundData.Sound.Playing = true
			end
			soundData.Sound.PlaybackSpeed = soundData.BasePlaybackSpeed + soundData.PlaybackSpeedIncrease * BoostTurbo
			soundData.Sound.Volume = soundData.VolumeIncrease * BoostTurbo

		elseif soundData.type == "SUP" then
			if soundData.Playing == false then
				soundData.Playing = true
				soundData.Sound.Playing = true
			end
			soundData.Sound.PlaybackSpeed = soundData.BasePlaybackSpeed + soundData.PlaybackSpeedIncrease * BoostSuper
			soundData.Sound.Volume = soundData.VolumeIncrease * BoostSuper

		elseif soundData.type == "BOV" then
			if (BoostTurbo - soundData.Sensitivity < prevBoost) and (tick()-lastBOV > 1) and Throttle < .8 then
				updateBOV = true
				soundData.Sound.PlaybackSpeed = soundData.BasePlaybackSpeed + soundData.PlaybackSpeedIncrease * BoostTurbo
				soundData.Sound.Volume = soundData.VolumeIncrease * BoostTurbo
				soundData.Sound.TimePosition = 0
				soundData.Sound.Playing = true
			end
			prevBoost = BoostTurbo

		elseif soundData.type == "IGN" then
			if Starting then
				if not soundData.Sound:GetAttribute("Played") then
					soundData.Sound.TimePosition = 0
					soundData.Sound.Playing = true
					soundData.Playing = true
				end
				soundData.Sound:SetAttribute("Played", true)
				soundData.Sound.Volume = soundData.Volume
				soundData.Sound.PlaybackSpeed = soundData.Pitch + (soundData.MaxPitch - soundData.Pitch) * (RPM/_Tune.IgnitionRPM)
			else
				soundData.Sound:SetAttribute("Played", false)
				if soundData.Playing == true then
					soundData.Playing = false
					soundData.Sound.Playing = false
				end
			end

		end
	end
	if updateBOV then
		lastBOV = tick()
		updateBOV = false
	end
	lastLoop = tick()
end

event.OnServerEvent:Connect(update)

-- Credit to Benkeeeee for solving a bug here for the community 💕
car.DriveSeat.ChildAdded:Connect(function(child: Instance)
	if child:IsA("Weld") and child.Name == "SeatWeld" then
		canUpdate = true
		for _, soundData in sounds do
			if soundData.type == "BOV" or soundData.type == "IGN" then
				continue
			end
			soundData.Sound.Volume = 0
			soundData.Sound:Play()
		end
	end
end)
car.DriveSeat.ChildRemoved:Connect(function(child: Instance)
	if child:IsA("Weld") and child.Name == "SeatWeld" then
		canUpdate = false
		for _, soundData in sounds do
			soundData.Sound:Stop()
		end
	end
end)

local children = nil
for i, desc in car["A-Chassis Tune"]["A-Chassis Interface"]:GetDescendants() do
	if desc:IsA("LocalScript") and desc:GetAttribute("ID") == "A-Chassis Sounds" then
		children = desc:GetChildren()
		break
	end
end

create(children)