Pinkstack/roblox-luau-corpus-text huggingface dataset:
--		if RunService:IsStudio() then
--			return true
--		end


		local success, canChat = pcall(function()
			return Chat:CanUserChatAsync(playerObj.UserId)
		end)
		return success and canChat
	end

	local function ValidateChatFunction(speakerName, message, channel)
		local speakerObj = ChatService:GetSpeaker(speakerName)
		local playerObj = speakerObj:GetPlayer()
		if not speakerObj then return false end
		if not playerObj then return false end

		if (playerObj.UserId < 1) then -- and (not RunService:IsStudio()) then
			return true
		end

		if not CanUserChat(playerObj) then
			speakerObj:SendSystemMessage(ChatLocalization:Get("GameChat_ChatMessageValidator_SettingsError","Your chat settings prevent you from sending messages."), channel)
			return true
		end

		if message:len() > ChatSettings.MaximumMessageLength + 1 then
			speakerObj:SendSystemMessage(ChatLocalization:Get("GameChat_ChatMessageValidator_MaxLengthError","Your message exceeds the maximum message length."), channel)
			return true
		end

		for i = 1, #DISALLOWED_WHITESPACE do
			if string.find(message, DISALLOWED_WHITESPACE[i]) then
				speakerObj:SendSystemMessage(ChatLocalization:Get("GameChat_ChatMessageValidator_WhitespaceError","Your message contains whitespace that is not allowed."), channel)
				return true
			end
		end
		return false
	end

	ChatService:RegisterProcessCommandsFunction("message_validation", ValidateChatFunction, ChatSettings.LowPriority)
end

return Run
--These are all donations that can be done. You must set this up with your own products to make money of it.
--To make a product you have to make a Developer Product in your game's settings page.
--You can make as many as you want, Once you made one, copy it's ID and past it in the 'ProductId' variable in this script. 
--Change the 'ProductPrice' to what the Developer Product Costs.




local module = {}	
module.Products = {
	
	{
		ProductPrice = 5, --The price from the Developer Product.
		ProductId = 1234585054 -- The ID from the Developer Product.	
	},
	{
		ProductPrice = 10, --The price from the Developer Product.
		ProductId = 1234585087 -- The ID from the Developer Product.	
	},
	{
		ProductPrice = 50, --The price from the Developer Product.
		ProductId = 1234585114 -- The ID from the Developer Product.	
	},
	{
		ProductPrice = 100, --The price from the Developer Product.
		ProductId = 1234585216 -- The ID from the Developer Product.	
	},
	
}
-- << Functions >> --



UserInputService.MouseIconEnabled = false

function lerp(a, b, c)
	return a + (b - a) * c
end

bobbing = game:GetService("RunService").RenderStepped:Connect(function(deltaTime)
	deltaTime = deltaTime * 60
	if Humanoid.Health <= 0 then
		bobbing:Disconnect()
		return
	end
	local rootMagnitude = Humanoid.RootPart and Vector3.new(Humanoid.RootPart.Velocity.X, 0, Humanoid.RootPart.Velocity.Z).Magnitude or 0
	local calcRootMagnitude = math.min(rootMagnitude, 50)
	if deltaTime > 3 then
		func1 = 0
		func2 = 0
	else
		func1 = lerp(func1, math.cos(tick() * 0.5 * math.random(10, 15)) * (math.random(5, 20) / 200) * deltaTime, 0.05 * deltaTime)
		func2 = lerp(func2, math.cos(tick() * 0.5 * math.random(5, 10)) * (math.random(2, 10) / 200) * deltaTime, 0.05 * deltaTime)
	end
	Camera.CFrame = Camera.CFrame * (CFrame.fromEulerAnglesXYZ(0, 0, math.rad(func3)) * CFrame.fromEulerAnglesXYZ(math.rad(func4 * deltaTime), math.rad(val * deltaTime), val2) * CFrame.Angles(0, 0, math.rad(func4 * deltaTime * (calcRootMagnitude / 5))) * CFrame.fromEulerAnglesXYZ(math.rad(func1), math.rad(func2), math.rad(func2 * 10)))
	val2 = math.clamp(lerp(val2, -Camera.CFrame:VectorToObjectSpace((Humanoid.RootPart and Humanoid.RootPart.Velocity or Vector3.new()) / math.max(Humanoid.WalkSpeed, 0.01)).X * 0.08, 0.1 * deltaTime), -0.35, 0.2)
	func3 = lerp(func3, math.clamp(UserInputService:GetMouseDelta().X, -5, 5), 0.25 * deltaTime)
	func4 = lerp(func4, math.sin(tick() * int) / 5 * math.min(1, int2 / 10), 0.25 * deltaTime)
	if rootMagnitude > 1 then
		val = lerp(val, math.cos(tick() * 0.5 * math.floor(int)) * (int / 200), 0.25 * deltaTime)
	else
		val = lerp(val, 0, 0.05 * deltaTime)
	end
	if rootMagnitude > 12 then
		int = 20
		int2 = 18
	elseif rootMagnitude > 0.1 then
		int = 12
		int2 = 14
	else
		int2 = 0
	end
	Player.CameraMaxZoomDistance = 0.5
	Player.CameraMinZoomDistance = 0.5
	vect3 = lerp(vect3, Camera.CFrame.LookVector, 0.125 * deltaTime)
end)
-- UI elements



local intro = script.Parent:WaitForChild("Intro")
intro.Visible = true

wait(2)

game.Workspace.Camera.CameraType = Enum.CameraType.Scriptable
game.Workspace.Camera.CFrame = CFrame.new(game.Workspace.IntroTunnel.CameraOrigin.Position,game.Workspace.IntroTunnel.CameraFace.Position)

local topStatus = script.Parent:WaitForChild("TopStatus")
topStatus.Text = status.Value

status:GetPropertyChangedSignal("Value"):Connect(function()
	topStatus.Text = status.Value
end)

intro.Play.MouseButton1Click:Connect(function()
	
	if game.ReplicatedStorage.MapVoting.Value == true then
		script.Parent.MapVoting.Visible = true
	end
	
	intro.Visible = false
	topStatus.Visible = true
	game.ReplicatedStorage.MenuPlay:FireServer()
	game.Workspace.Camera.CameraType = Enum.CameraType.Custom
end)

game.ReplicatedStorage.KillFeed.OnClientEvent:Connect(function(text)
	script.Parent.KillFeed.Visible = true
	script.Parent.KillFeed.Text = text
	wait(2)
	script.Parent.KillFeed.Visible = false
end)

game.ReplicatedStorage.Announcement.OnClientEvent:Connect(function(text)
	script.Parent.Announcement.Text = text
	script.Parent.Announcement.Visible = true
	wait(5)
	script.Parent.Announcement.Visible = false
end)
-----------------
--| Functions |--
-----------------



local function OnActivated()
	local myModel = MyPlayer.Character
	if Tool.Enabled and myModel and myModel:FindFirstChild('Humanoid') and myModel.Humanoid.Health > 0 then
		Tool.Enabled = false

		-- Create a clone of Rocket and set its color
		local rocketClone = Rocket:Clone()
		DebrisService:AddItem(rocketClone, 30)
		rocketClone.BrickColor = MyPlayer.TeamColor

		-- Position the rocket clone and launch!
		local spawnPosition = (ToolHandle.CFrame * CFrame.new(2, 0, 0)).p
		rocketClone.CFrame = CFrame.new(spawnPosition, myModel.Humanoid.TargetPoint) --NOTE: This must be done before assigning Parent
		rocketClone.Velocity = rocketClone.CFrame.lookVector * ROCKET_SPEED --NOTE: This should be done before assigning Parent
		rocketClone.Parent = Workspace

		wait(RELOAD_TIME)

		Tool.Enabled = true
	end
end
--Gear Ratios


Tune.FinalDrive		= 2.78			-- Gearing determines top speed and wheel torque
Tune.Ratios			= {				-- Higher ratio = more torque, Lower ratio = higher top speed
	--[[Reverse]]	2.34		,	-- Copy and paste a ratio to add a gear
	--[[Neutral]]	0			,	-- Ratios can also be deleted
	--[[ 1 ]]		3.53		,	-- Reverse, Neutral, and 1st gear are required
	--[[ 2 ]]		2.04		,
	--[[ 3 ]]		1.38		,
	--[[ 4 ]]		1.03		,
	--[[ 5 ]]		0.81		,
	--[[ 6 ]]		0.64		,
	--[[ 7 ]]		0.48		,		
}
Tune.FDMult			= 1			-- Ratio multiplier (Change this value instead of FinalDrive if car is struggling with torque ; Default = 1)
-- FUNCTIONS --


CS:GetInstanceAddedSignal("OnFire"):Connect(function(Character)
	local Humanoid = Character:WaitForChild("Humanoid")
	Replicate:FireAllClients("Burn", Character, true)
	coroutine.wrap(function()
		repeat 
			Humanoid:TakeDamage(math.random(4, 12))
			task.wait(1)
		until not CS:HasTag(Character, "OnFire")
	end)()
end)

CS:GetInstanceRemovedSignal("OnFire"):Connect(function(Character)
	Replicate:FireAllClients("Burn", Character, false)
end)
--[[
	Commands:
	/kick
	/explode
	/trip
	/visible
	/invisible
]]



admin.kick = {
	["Command"] = "Kick",
	["Place"] = 1,
	
	["Function"] = function(arguments)
		local players = {}

		for i, playerName in pairs(arguments) do
			local player = findPlayer(playerName)

			if player then
				disconnect(player)
			end
		end
	end
}

admin.explode = {
	["Command"] = "Explode",
	["Place"] = 2,

	["Function"] = function(arguments)
		for i, playerName in pairs(arguments) do
			local player = findPlayer(playerName)

			if player then
				explode(player)
			end
		end
	end
}

admin.trip = {
	["Command"] = "Trip",
	["Place"] = 3,

	["Function"] = function(arguments)
		for i, playerName in pairs(arguments) do
			local player = findPlayer(playerName)

			if player then
				local humanoid = player.Character:FindFirstChild("Humanoid")

				if not humanoid then
					return
				end

				humanoid.PlatformStand = true
				task.wait(1.5)
				humanoid.PlatformStand = false
			end
		end
	end
}

admin.visible = {
	["Command"] = "Visible",
	["Place"] = 4,

	["Function"] = function(arguments)
		for i, playerName in pairs(arguments) do
			local player = findPlayer(playerName)

			if player then
				local character = player.Character or player.CharacterAdded:Wait()

				for i, v in pairs(character:GetDescendants()) do
					if v:IsA("Part") or v:IsA("BasePart") or v:IsA("MeshPart") then
						v.Transparency = 0
					end
				end

				character.HumanoidRootPart.Transparency = 1
				character.HitBox.Transparency = 1
			end
		end
	end
}

admin.invisible = {
	["Command"] = "Invisible",
	["Place"] = 5,

	["Function"] = function(arguments)
		for i, playerName in pairs(arguments) do
			local player = findPlayer(playerName)

			if player then
				local character = player.Character or player.CharacterAdded:Wait()

				for i, v in pairs(character:GetDescendants()) do
					if v:IsA("Part") or v:IsA("BasePart") then
						v.Transparency = 1
					end
				end

				character.HumanoidRootPart.Transparency = 1
				character.HitBox.Transparency = 1
			end
		end
	end
}

return admin
--// WIP


local function LoadPackage(package, folder, runNow)
	--// runNow - Run immediately after unpacking (default behavior is to just unpack (((only needed if loading after startup))))
	--// runNow currently not used (limitations) so all packages must be present at server startup
	local function unpackFolder(curFolder, unpackInto)
		if unpackInto then
			for _, obj in ipairs(curFolder:GetChildren()) do
				local clone = obj:Clone()
				if obj:IsA("Folder") then
					local realFolder = unpackInto:FindFirstChild(obj.Name)
					if not realFolder then
						clone.Parent = unpackInto
					else
						unpackFolder(obj, realFolder)
					end
				else
					clone.Parent = unpackInto
				end
			end
		else
			warn("Missing parent to unpack into for ".. tostring(curFolder))
		end
	end

	unpackFolder(package, folder)
end;

local function CleanUp()
	--local env = getfenv(2)
	--local ran,ret = pcall(function() return env.script:GetFullName() end)
	warn("Beginning Adonis cleanup & shutdown process...")
	--warn("CleanUp called from "..tostring((ran and ret) or "Unknown"))
	--local loader = server.Core.ClientLoader
	server.Model.Name = "Adonis_Loader"
	server.Model.Parent = service.ServerScriptService
	server.Running = false

	pcall(service.Threads.StopAll)
	pcall(function()
		for i, v in pairs(RbxEvents) do
			print("Disconnecting event")
			v:Disconnect()
			table.remove(RbxEvents, i)
		end
	end)
	--loader.Archivable = false
	--loader.Disabled = true
	--loader:Destroy()
	if server.Core and server.Core.RemoteEvent then
		pcall(server.Core.DisconnectEvent)
	end

	--[[delay(0, function()
		for i,v in next,server do
			server[i] = nil; --// Try to break it to prevent any potential hanging issues; Not very graceful...
		end
	--end)--]]

	warn("Unloading complete")
end;

server = {
	Running = true;
	Modules = {};
	Pcall = Pcall;
	cPcall = cPcall;
	Routine = Routine;
	LogError = logError;
	ErrorLogs = ErrorLogs;
	ServerStartTime = os.time();
	CommandCache = {};
};

locals = {
	server = server;
	CodeName = "";
	Settings = server.Settings;
	HookedEvents = HookedEvents;
	ErrorLogs = ErrorLogs;
	logError = logError;
	origEnv = origEnv;
	Routine = Routine;
	Folder = Folder;
	GetEnv = GetEnv;
	cPcall = cPcall;
	Pcall = Pcall;
};

service = require(Folder.Shared.Service)(function(eType, msg, desc, ...)
	local extra = {...}
	if eType == "MethodError" then
		if server and server.Logs and server.Logs.AddLog then
			server.Logs.AddLog("Script", {
				Text = "Cached method doesn't match found method: "..tostring(extra[1]);
				Desc = "Method: "..tostring(extra[1])
			})
		end
	elseif eType == "ServerError" then
		--print("Server error")
		logError("Server", msg)
	elseif eType == "TaskError" then
		--print("Task error")
		logError("Task", msg)
	end
end, function(c, parent, tab)
	if not isModule(c) and c ~= server.Loader and c ~= server.Dropper and c ~= server.Runner and c ~= server.Model and c ~= script and c ~= Folder and parent == nil then
		tab.UnHook()
	end
end, ServiceSpecific, GetEnv(nil, {server = server}))
----------------------------------
------------FUNCTIONS-------------
----------------------------------



function Jump()
	local character = Player.Character
	if character then
		local humanoid = character:FindFirstChild("Humanoid")
		if humanoid then
			humanoid.Jump = true
		end
	end
end
function HumanoidChanged(humanoid, property)
	--print(property)
	if property == "Jump" then
		humanoid.Jump = false
	elseif property == "Sit" then
		humanoid.Sit = true
	elseif property == "Parent" then
		Deactivate()
		Abort()
	end
end
function HumanoidDied()
	Deactivate()
end
function SetCamera(cframe)
	Camera.CameraType = Enum.CameraType.Scriptable
	Camera:Interpolate(cframe, cframe + cframe.lookVector, .5)
	--Camera.CoordinateFrame = cframe
end
function ReturnCamera()
	Camera.CameraType = Enum.CameraType.Custom
end
--[[
game.ReplicatedStorage.ToggleSleeping.OnServerEvent:Connect(function()
	workspace.Ignore.Zombies.Snowfella.RootPart:SetAttribute("Sleeping", not workspace.Ignore.Zombies.Snowfella.RootPart:GetAttribute("Sleeping"))
end)
]]



game.Players.PlayerAdded:Connect(function(ply)
	ply:WaitForChild("InGame").Value = true
	
	--[[
	ply.CharacterAdded:Connect(function()
		ply:WaitForChild("InGame").Value = true
	end)
	]]
end)
--local GROUP_ID = replace with wanted group ID(s), separate with comma
--local GROUP_RANK = replace with wanted group rank(s), e.g. 255, separate wtih comma



return function (registry)
	registry:RegisterHook("BeforeRun", function(context)
		if context.Group == "DefaultAdmin" and context.Executor.UserId ~= game.CreatorId then
			return "You don't have permission to run this command"
		end
		--if context.Group == "DefaultAdmin" and not (context.Executor:GetRankInGroup(GROUP_ID) == GROUP_RANK) then
			--return "You don't have permission to run this command"
		--end
		--remove --s here to enable for group usage
		registry:RegisterHook("AfterRun", function(context)
			print(context.Response) -- see the actual response from the command execution
			return "Command has been run."
		end)
	end)
end
--Returns whether the given position is under cover


local function UnderObject(pos, l)
	l = l or 120
	
	local hit, position = Workspace:FindPartOnRayWithIgnoreList(Ray.new(pos, UpVec * l), ignoreList)
	if hit then
		return hit.Transparency ~= 1 and true or UnderObject(position + UpVec, l - (pos - position).Magnitude)
	else
		return false
	end
end
--[=[
	@param object any -- Object to remove
	Removes the object from the Trove and cleans it up.
	```lua
	local part = Instance.new("Part")
	trove:Add(part)
	trove:Remove(part)
	```
]=]


function Trove:Remove(object: any): boolean
	local objects = self._objects
	for i,obj in ipairs(objects) do
		if obj[1] == object then
			local n = #objects
			objects[i] = objects[n]
			objects[n] = nil
			self:_cleanupObject(obj[1], obj[2])
			return true
		end
	end
	return false
end
--[=[
	Constructs a new symbol

	@param id any -- Identifier for the symbol (usually a string)
	@param scope Symbol? -- Optional symbol scope
	@return boolean -- Returns `true` if the `obj` parameter is a Symbol
]=]


function Symbol.new(id: any, scope: any)
	assert(id ~= nil, "Symbol ID cannot be nil")
	if scope ~= nil then
		assert(Symbol.Is(scope), "Scope must be a Symbol or nil")
	end
	local self = setmetatable({
		ClassName = CLASSNAME;
		_id = id;
		_scope = scope;
	}, Symbol)
	return self
end
-- Smart Circle mode needs the intersection of 2 rays that are known to be in the same plane
-- because they are generated from cross products with a common vector. This function is computing
-- that intersection, but it's actually the general solution for the point halfway between where
-- two skew lines come nearest to each other, which is more forgiving.


local function RayIntersection(p0: Vector3, v0: Vector3, p1: Vector3, v1: Vector3): Vector3
	local v2 = v0:Cross(v1)
	local d1 = p1.X - p0.X
	local d2 = p1.Y - p0.Y
	local d3 = p1.Z - p0.Z
	local denom = Det3x3(v0.X,-v1.X,v2.X,v0.Y,-v1.Y,v2.Y,v0.Z,-v1.Z,v2.Z)

	if (denom == 0) then
		return ZERO_VECTOR3 -- No solution (rays are parallel)
	end

	local t0 = Det3x3(d1,-v1.X,v2.X,d2,-v1.Y,v2.Y,d3,-v1.Z,v2.Z) / denom
	local t1 = Det3x3(v0.X,d1,v2.X,v0.Y,d2,v2.Y,v0.Z,d3,v2.Z) / denom
	local s0 = p0 + t0 * v0
	local s1 = p1 + t1 * v1
	local s = s0 + 0.5 * ( s1 - s0 )

	-- 0.25 studs is a threshold for deciding if the rays are
	-- close enough to be considered intersecting, found through testing
	if (s1-s0).Magnitude < 0.25 then
		return s
	else
		return ZERO_VECTOR3
	end
end
--Internal functions


function DataStore:Debug(...)
	if self.debug then
		print(...)
	end
end

function DataStore:_GetRaw()
	if self.getRawPromise then
		return self.getRawPromise
	end

	self.getRawPromise = self.savingMethod:Get():andThen(function(value)
		self.value = value
		self:Debug("value received")
		self.haveValue = true
	end):finally(function()
		self.getting = false
	end)

	return self.getRawPromise
end

function DataStore:_Update(dontCallOnUpdate)
	if not dontCallOnUpdate then
		for _,callback in pairs(self.callbacks) do
			callback(self.value, self)
		end
	end

	self.haveValue = true
	self.valueUpdated = true
end
-- FUNCTIONS --


return function(TargetCF)
	
	local spikes = FX.Club.Z:Clone()
	spikes.PrimaryPart.CFrame = TargetCF
	
	local originalSizeList = {
		spikes.Floor["1"].Size,
		spikes.Floor["2"].Size,
		spikes.Floor["3"].Size,
		spikes.Floor["4"].Size,
		spikes.Floor["5"].Size,
		spikes.Floor["6"].Size,
		spikes.Floor["7"].Size,
		spikes.Floor["8"].Size,
		spikes.Floor["9"].Size,
		spikes.Floor["10"].Size,
		spikes.Floor["11"].Size,
	}
	
	local floorSize = spikes.Floor.Size
	spikes.Floor.Size = Vector3.new()
	spikes.Floor.Transparency = 1
	
	coroutine.wrap(function() -- THREAD TO MAKE SPIKES INVISIBLE
		for i, v in pairs(spikes.Floor:GetChildren()) do
			if v:IsA("MeshPart") then
				v.Transparency = 1
				v.Size = Vector3.new()
			end
		end
	end)()
	
	spikes.Parent = workspace.Debris
	
	local impactSFX = RS.Sounds.Club.Z.Impact:Clone()
	impactSFX.Parent = spikes.Floor
	impactSFX:Play()
	
	local expSFX = RS.Sounds.Club.Z.Explosion:Clone()
	expSFX.Parent = spikes.Floor
	expSFX:Play()
	
	coroutine.wrap(function()
		for i, v in pairs(spikes.Floor:GetChildren()) do
			if v:IsA("MeshPart") then
				local spikeIndex = tonumber(v.Name)
				v.Transparency = 0
				TS:Create(v, TweenInfo.new(0.18, Enum.EasingStyle.Back, Enum.EasingDirection.Out), {Size = originalSizeList[spikeIndex]}):Play()
				
				local dustFX = spikes.Dust:Clone()
				dustFX.Parent = v
				dustFX:Emit(dustFX:GetAttribute("EmitCount"))
				
				task.wait(0.015)
			end
		end
	end)()
	
	spikes.Floor.Transparency = 0
	TS:Create(spikes.Floor, TweenInfo.new(0.25, Enum.EasingStyle.Back, Enum.EasingDirection.Out), {Size = floorSize}):Play()
	
	local dustFX = spikes.Dust:Clone()
	dustFX.Parent = spikes.Floor
	dustFX:Emit(dustFX:GetAttribute("EmitCount"))
	
	debrisModule.Shockwave(TargetCF.Position, 25, 55)
	
	task.delay(4, function()
		--coroutine.wrap(function()
			for i, v in pairs(spikes.Floor:GetChildren()) do
				if v:IsA("MeshPart") then
					local spikeIndex = tonumber(v.Name)
					TS:Create(v, TweenInfo.new(0.35, Enum.EasingStyle.Back, Enum.EasingDirection.In), {Size = Vector3.new()}):Play()
					task.wait(0.025)
				end
			end
		--end)()
		task.wait(0.3)

		TS:Create(spikes.Floor, TweenInfo.new(0.35, Enum.EasingStyle.Back, Enum.EasingDirection.In), {Size = Vector3.new()}):Play()
		game.Debris:AddItem(spikes, 2)
	end)
end
--[[[Default Controls]]


	--Peripheral Deadzones
	Tune.Peripherals = {
		MSteerWidth				= 67		,	-- Mouse steering control width	(0 - 100% of screen width)
		MSteerDZone				= 10		,	-- Mouse steering deadzone (0 - 100%)
		
		ControlLDZone			= 3			,	-- Controller steering L-deadzone (0 - 100%)
		ControlRDZone			= 3			,	-- Controller steering R-deadzone (0 - 100%)
	}
	
			Tune.KickdownWindow = .25 --Time (seconds) in which you must double click throttle key 
	Tune.KickdownRPMCap = 5000 --RPM at which downshifts will no longer occur in the event of a kickdown

	Tune.LaunchBuildup = 6000 --RPM in which launch control will build up to
	
	--Control Mapping
	Tune.Controls = {
		
	--Keyboard Controls
		--Mode Toggles
		ToggleTCS				= Enum.KeyCode.T					,
		ToggleABS				= Enum.KeyCode.Y					,
		ToggleTransMode			= Enum.KeyCode.M					,
		
		
		--Primary Controls
		Throttle				= Enum.KeyCode.Up					,
		Brake					= Enum.KeyCode.Down					,
		SteerLeft				= Enum.KeyCode.Left					,
		SteerRight				= Enum.KeyCode.Right				,
		
		--Secondary Controls
		Throttle2				= Enum.KeyCode.W					,
		Brake2					= Enum.KeyCode.S					,
		SteerLeft2				= Enum.KeyCode.A					,
		SteerRight2				= Enum.KeyCode.D					,
		
		--Manual Transmission
		ShiftUp					= Enum.KeyCode.E					,
		ShiftDown				= Enum.KeyCode.Q					,
		Clutch					= Enum.KeyCode.LeftShift			,
		
		--Handbrake
		PBrake					= Enum.KeyCode.P				,
		
	--Mouse Controls
		MouseThrottle			= Enum.UserInputType.MouseButton1	,
		MouseBrake				= Enum.UserInputType.MouseButton2	,
		MouseClutch				= Enum.KeyCode.W					,
		MouseShiftUp			= Enum.KeyCode.E					,
		MouseShiftDown			= Enum.KeyCode.Q					,
		MousePBrake				= Enum.KeyCode.LeftShift			,
		
	--Controller Mapping
		ContlrThrottle			= Enum.KeyCode.ButtonR2				,
		ContlrBrake				= Enum.KeyCode.ButtonL2				,
		ContlrSteer				= Enum.KeyCode.Thumbstick1			,
		ContlrShiftUp			= Enum.KeyCode.ButtonY				,
		ContlrShiftDown			= Enum.KeyCode.ButtonX				,
		ContlrClutch			= Enum.KeyCode.ButtonR1				,
		ContlrPBrake			= Enum.KeyCode.ButtonL1				,
		ContlrToggleTMode		= Enum.KeyCode.DPadUp				,
		ContlrToggleTCS			= Enum.KeyCode.ButtonA			    ,
		ContlrToggleABS			= Enum.KeyCode.ButtonA		     	,
	}
-- An optional function that an Action's onActivated callback can return. This
-- function gets called when the action is being deactivated so it can clean
-- itself up


export type ActionTeardown = () -> ()

export type Action = {
	-- The name of the action. This is shown first in tooltips
	name: string,

	-- A description of the action. This is shown after the name in tooltips
	description: string?,

	-- Icon displayed on the action bar
	icon: string,

	-- Icon displayed on the action bar when the action is active. This can only
	-- be used on parent actions, not sub-actions
	activeIcon: string?,

	-- Optional list of sub-actions. This allows you to create submenus of
	-- various other actions
	actions: { Action }?,

	-- The parent is only set for sub-actions and points to the action that
	-- contains it
	parent: Action?,

	-- Can be set to `true` for a sub-action that acts as the default option
	isDefault: boolean?,

	-- Optional callback that gets run when activating an action or sub-action.
	-- Typically if an action contains sub-actions, only the sub-actions will
	-- have onActivated defined
	onActivated: ((self: Action, store: any) -> ActionTeardown?)?,
}

export type State = {
	-- The last action that was activated. This can be any of the top-level
	-- actions or a sub-action
	lastActivatedAction: Action?,

	-- The current action that was activated that contains sub-actions
	parentAction: Action?,

	-- The active actions are stored in the state so that when an action is
	-- toggled it triggers a re-render.
	activeActions: { Action },

	-- Whether or not the submenu is shown. This happens when an action that has
	-- sub-actions has been activated
	isSubmenuShown: boolean,
}

return {}