--[[
		             _____ _                   _
	     /\         / ____| |                 (_)
	    /  \ ______| |    | |__   __ _ ___ ___ _ ___
	   / /\ \______| |    | '_ \ / _` / __/ __| / __|
	  / ____ \     | |____| | | | (_| \__ \__ \ \__ \
	 /_/    \_\     \_____|_| |_|\__,_|___/___/_|___/
	 Version 1.7
--]]
-- By detocoyote
-- You can edit the sound properties in the sounds under the script, or you can change where sounds are stored and if there is Filtering Enabled in here.

local car = script.Parent.Car.Value
local values = script.Parent.Values
local event = car:WaitForChild("Sounds")

-- [[ EDIT ]] -------------------------------------------------------------------------

local FE			= true										-- If true, this makes sounds play for everyone in the game, not just the driver.
local FE_RATE		= 1/15										-- FE must equal true. This changes how often the sound updates for the player. Higher = updates less often.
local DIRECTORY		= car.Body.Sounds					-- This is the path of the sounds group or model to use.

-- [[ END EDIT ]] -------------------------------------------------------------------------

local RunService = game:GetService("RunService")

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

local ThrottleValue = values.Throttle
local RPMValue = values.RPM
local GearValue = values.Gear
local ClutchValue = values.Clutch
local BoostTurboValue = values.BoostTurbo
local BoostSuperValue = values.BoostSuper
local StartingValue = script.Parent.IsOn.Starting
local IsOnValue = script.Parent.IsOn

-- Index all the sounds
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
for _, sound in script:GetChildren() do
	if not sound:IsA("Sound") then continue end

	local soundParent = DIRECTORY:FindFirstChild(sound:GetAttribute("SoundParent")) or car.Body["#Weight"]
	local newSound = soundParent:FindFirstChild(sound.Name.."_Local")

	local soundData = {}
	if not newSound then
		newSound = sound:Clone()
		newSound.Name = newSound.Name.."_Local"

		newSound.Playing = false
		newSound.Volume = 0
		newSound.Parent = soundParent

		-- Do it the longer way so the user can add their own effects
		local eq = Instance.new("EqualizerSoundEffect", newSound)
		eq.Name = "#EQ"
		eq.HighGain = 0
		eq.MidGain = 0
		eq.LowGain = 0
		local dist = Instance.new("DistortionSoundEffect", newSound)
		dist.Name = "#Distortion"
		dist.Level = 0

		soundData.EQ = eq
		soundData.Distortion = dist
	end

	if FE then
		local serverSound = soundParent:FindFirstChild(sound.Name)
		if serverSound then
			serverSound:SetAttribute("SoundId", serverSound.SoundId)
			serverSound.SoundId = ""
			soundData.ServerSound = serverSound
		end
	end

	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)

	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

	soundData.Sound = newSound
	soundData.Playing = false
	soundData.type = string.sub(sound.Name, 1, 3)
	soundData.EQ = newSound:FindFirstChild("#EQ")
	soundData.Distortion = newSound:FindFirstChild("#Distortion")

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

local function f(m, n, p, q, M, b, x) -- Calculation for VVL
	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

--man i love linear interpolation
local updateBOV = false
local lastBOV = tick()
local last = tick()
local prevBoost = 0
local noCustomEngine = car["A-Chassis Tune"].README:FindFirstChild("CustomEngine") == nil
local WheelSpeedsChildren = values.WheelSpeeds:GetChildren()
local function RuntimeLoop(dt)
	if tick()-last < 1/60 then return end
	last = tick()

	-- Calculate smooth RPM
	table.insert(rpmHistory, RPMValue.Value)
	if #rpmHistory > 8 then
		table.remove(rpmHistory, 1)
	end
	rpm = 0
	for _, v in rpmHistory do
		rpm = rpm + v
	end
	rpm = rpm / #rpmHistory

	-- Calculate VVL
	if _Tune.CustomEngineCalculations == false or noCustomEngine 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

	-- Transmission rotation speed
	AverageRotSpeed = 0
	local count = 0
	for _, wheel in WheelSpeedsChildren do
		if string.find(wheel.Name, "F") then
			if not (_Tune.Config == "FWD" or _Tune.Config == "AWD") then continue end
			AverageRotSpeed += wheel.Value
			count += 1
		else
			if not (_Tune.Config == "RWD" or _Tune.Config == "AWD") then continue end
			AverageRotSpeed += wheel.Value
			count += 1
		end
	end
	AverageRotSpeed /= count

	local IsOn = IsOnValue.Value 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", ThrottleValue.Value)
			soundData.EQ.MidGain = eqGain(soundData.Sound, "MidGain", ThrottleValue.Value)
			soundData.EQ.LowGain = eqGain(soundData.Sound, "LowGain", ThrottleValue.Value)
			soundData.Distortion.Level = distGain(soundData.Sound, ThrottleValue.Value)
			soundData.Sound.PlaybackSpeed = soundData.BasePlaybackSpeed + (soundData.RedlinePlaybackSpeed - soundData.BasePlaybackSpeed) * (rpm/_Tune.Redline)
			soundData.Sound.Volume = IsOn * (soundData.BaseVolume + (soundData.ThrottleVolume*ThrottleValue.Value) + (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(GearValue.Value)) and (soundData.ClutchVolume + ((soundData.BaseVolume + soundData.VariableVolume*(AverageRotSpeed/(300*(Units.Length_mm/1000)*3.6))) - soundData.ClutchVolume) * (1-ClutchValue.Value) + soundData.ThrottleVolume*ThrottleValue.Value) 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 * BoostTurboValue.Value
			soundData.Sound.Volume = soundData.VolumeIncrease * BoostTurboValue.Value

		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 * BoostSuperValue.Value
			soundData.Sound.Volume = soundData.VolumeIncrease * BoostSuperValue.Value

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

		elseif soundData.type == "IGN" then
			if StartingValue.Value 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
end
RunService.Heartbeat:Connect(RuntimeLoop)

car.DriveSeat.ChildRemoved:Connect(function(child)
	if child:IsA("Weld") and child.Name == "SeatWeld" then
		for _, soundData in sounds do
			soundData.Sound.Playing = false
			soundData.ServerSound.Playing = false
			if soundData.ServerSound then
				soundData.ServerSound.SoundId = soundData.ServerSound:GetAttribute("SoundId")
			end
		end
		script:Destroy()
	end
end)

local function FELoop()
	while task.wait(FE_RATE) do
		local packet = buffer.create(17)
		buffer.writeu16(packet, 0, math.clamp(math.round(rpm), 0, 65535))
		buffer.writeu8(packet, 2, math.clamp(math.round(ThrottleValue.Value*255), 0, 255))
		buffer.writeu16(packet, 3, math.clamp(math.round(AverageRotSpeed), 0, 65535))
		buffer.writei8(packet, 5, GearValue.Value)
		buffer.writeu8(packet, 6, math.clamp(math.round(ClutchValue.Value*255), 0, 255))
		buffer.writef32(packet, 7, math.round(BoostTurboValue.Value*100)/100)
		buffer.writef32(packet, 11, math.round(BoostSuperValue.Value*100)/100)
		buffer.writeu8(packet, 15, StartingValue.Value and 1 or 0)
		buffer.writeu8(packet, 16, IsOnValue.Value and 1 or 0)
		event:FireServer(packet)
	end
end
if FE then
	task.spawn(FELoop)
end


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

local UserInputService = game:GetService("UserInputService")
local HapticService = game:GetService("HapticService")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")

local car = script.Parent.Parent.Car.Value
local values = script.Parent.Parent.Values
local event = car.Friction
local player= Players.LocalPlayer

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

local SimulationRate = 1/24

local VelocityValue = values.Velocity

-- Rumble
local isVibrationSupported = HapticService:IsVibrationSupported(Enum.UserInputType.Gamepad1)
UserInputService.GamepadConnected:Connect(function()
	isVibrationSupported = HapticService:IsVibrationSupported(Enum.UserInputType.Gamepad1)
end)

-- Hide UI
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)

task.wait()

--Wheels Array
--Wheels Array
-- Wheels Array
local WheelData = {}

local wheelsFolder = car:FindFirstChild("Wheels")
if not wheelsFolder then
	return -- vehicle is not valid anymore (seat detached / rebuild state)
end

for _, v in wheelsFolder:GetChildren() do
	local data = {}
	data.Object = v
	data.Temperature = _WheelTune.AmbientTemp
	data.Dirt = 0
	data.Stress = 0
	data.Ray = nil
	data.Material = ""
	data.SmokePart = v:WaitForChild("SmokePart")
	data.ID = v:GetAttribute("ID")
	data.Arm = v.Arm

	if _WheelTune.TireFlex then
		data.FlexCollision = data.Object:WaitForChild("Collision")
	end
	if data.FlexCollision then
		data.FlexCollision:WaitForChild("#GRAVCOMP")
	end

	WheelData[v.Name] = data
end

--Open/Load Heat Data
if _WheelTune.SaveHeatData then
	local wd = car:FindFirstChild("HeatData")
	if wd then
		local t = tick()-wd.STime.Value
		for name, data in WheelData do
			if wd:FindFirstChild(name) then
				local front = string.find(name, "F")
				data.Temperature = wd[name]:GetAttribute("Temperature") - 60*t*(front and _WheelTune.FCooling or _WheelTune.RCooling)
				data.Dirt = wd[name]:GetAttribute("Dirt")
			end
		end
	end
end

--Close/Store Heat Data
car.DriveSeat.ChildRemoved:Connect(function(child)
	if child.Name == "SeatWeld" then

		-- Save heat data
		if _WheelTune.SaveHeatData then
			local wd = car:FindFirstChild("HeatData")
			if not wd then
				wd = script.Parent.HeatData:Clone()
				wd.Parent = car
			end

			for name, data in WheelData do
				local config = wd:FindFirstChild(name) or Instance.new("Configuration")
				config.Name = name
				config.Parent = wd
				config:SetAttribute("Temperature", data.Temperature)
				config:SetAttribute("Dirt", data.Dirt)
			end

			wd.STime.Value = tick()
		end

		-- Stop haptics
		if isVibrationSupported then
			HapticService:SetMotor(Enum.UserInputType.Gamepad1, Enum.VibrationMotor.Small, 0)
		end

		-- Stop smoke safely
		if _WheelTune.SmokeEffects == "Local" or _WheelTune.SmokeEffects == "All" then
			local wheelsFolder = car:FindFirstChild("Wheels")
			if wheelsFolder then
				for _, wheel in wheelsFolder:GetChildren() do
					if not wheel:IsA("BasePart") then continue end
					local smokePart = wheel:FindFirstChild("SmokePart")
					if not smokePart then continue end

					for _, d in smokePart:GetChildren() do
						if d:IsA("Sound") then
							d.Playing = false
							d.SoundId = d:GetAttribute("data") or d.SoundId
						elseif d:IsA("ParticleEmitter") then
							d.Enabled = false
							d.Lifetime = d:GetAttribute("data") or d.Lifetime
						elseif d:IsA("Trail") then
							d.Enabled = false
							d.Lifetime = d:GetAttribute("data") or d.Lifetime
						end
					end
				end
			end
		end

		script:Destroy()
	end
end)

--Ray Data (Detomiks)
local rayParams = RaycastParams.new()
rayParams.FilterDescendantsInstances = {car}
rayParams.FilterType = Enum.RaycastFilterType.Exclude
local materialSort = {

	road = {
		"Plastic",
		"Slate",
		"Concrete",
		"Granite",
		"Brick",
		"Pebble",
		"Cobblestone",
		"Rock",
		"Basalt",
		"Ground",
		"Pavement",
		"SmoothPlastic",
		"Asphalt",
	},

	dirt = {
		"Grass",
		"Mud",
		"Ground",
		"LeafyGrass",
	},

	sand = {
		"Sand",
		"Sandstone",
		"Limestone"
	},

	snow = {
		"Glacier",
		"Snow",
	}

}
local materialID = {
	road = 1,
	dirt = 2,
	sand = 3,
	snow = 4,
}
local lastInput = UserInputService:GetLastInputType()
UserInputService.LastInputTypeChanged:Connect(function(inputType)
	lastInput = inputType
end)

--Runtime Loops
local numWheels = 0
for _, v in WheelData do
	numWheels = numWheels + 1
end
local dtTable = {}
for name, data in WheelData do
	-- Replicate effects to the client
	data.Sounds = {}
	data.Trails = {
		road = {},
		dirt = {},
		snow = {},
		sand = {}
	}
	data.Smokes = {}
	if _WheelTune.SmokeEffects == "Local" or _WheelTune.SmokeEffects == "All" then
		for _, v in data.SmokePart:GetChildren() do
			if string.find(v.Name, "Local_") then continue end
			if v:IsA("Trail") or v:IsA("ParticleEmitter") or v:IsA("Sound") then
				local effectType = (string.find(v.Name, "road") and "road") or (string.find(v.Name, "sand") and "sand") or (string.find(v.Name, "dirt") and "dirt") or (string.find(v.Name, "snow") and "snow")
				local effect = v
				if _WheelTune.SmokeEffects == "Local" then
					v.Name = "Local_"..v.Name
				else
					local new = data.SmokePart:FindFirstChild("Local_"..v.Name) or v:Clone()
					new.Name = "Local_"..v.Name
					new.Parent = data.SmokePart
					effect = new
					if v:IsA("Trail") then
						v:SetAttribute("data", v.Lifetime)
						v.Lifetime = 0
					elseif v:IsA("ParticleEmitter") then
						v:SetAttribute("data", v.Lifetime)
						v.Lifetime = NumberRange.new(0)
					else
						v:SetAttribute("data", v.SoundId)
						v.SoundId = ""
					end
				end
				if v:IsA("Trail") then
					data.Trails[effectType][math.round(v:GetAttribute("stress")*_WheelTune.TireMarkTransparencyLevels)] = effect
				elseif v:IsA("ParticleEmitter") then
					data.Smokes[effectType] = effect
				elseif v:IsA("Sound") then
					data.Sounds[effectType] = effect
				end
			end
		end
	end

	-- Cache other things
	data.SmokePartChildren = data.SmokePart:GetChildren()
	data.CollisionModel = data.Object:FindFirstChild("CollisionModel")
	data.tireUI = script.Parent.Tires:FindFirstChild(name)
	data.FlexCollisionGravComp = data.FlexCollision and data.FlexCollision:FindFirstChild("#GRAVCOMP")
	data.ObjectGravComp = data.Object:FindFirstChild("#GRAVCOMP")
	data.Collision = data.Object:FindFirstChild("Collision")
	data.TrailAttachmentPart = data.Object:FindFirstChild("TrailAttachmentPart")
	data.FlexCollisionAlignPosition = data.FlexCollision and data.FlexCollision.AlignPosition
	data.CurrentSound = nil
	data.CurrentSmoke = nil
	data.CurrentTrail = nil
	data.TrailStress = 0
	for _, d in data.SmokePartChildren do
		if d:IsA("Sound") and d:FindFirstChild("TremoloSoundEffect") then
			d:SetAttribute("HasTremolo", true)
		end
	end

	local sandForce = Vector3.new()
	local last = tick()

	local front = string.find(name, "F")

	local function TireFlexPhysics(dt)
		dtTable[name] = dt
		if not (_WheelTune.TireFlex and data.FlexCollision) then return end
		local radius = (data.Object.Size.Y/2)
		local h = (data.FlexCollision.Position - data.Object.Position).Magnitude/radius
		local y = 2 * radius * math.sqrt(1-(1-h)^2)
		local area = y * data.Object.Size.X
		if front then
			data.FlexCollisionAlignPosition.MaxForce = (_WheelTune.FPressureForce) * (area*(Units.Length_mm*0.03937008)^2) * 4.448222 * Units.Force_N
		else
			data.FlexCollisionAlignPosition.MaxForce = (_WheelTune.RPressureForce) * (area*(Units.Length_mm*0.03937008)^2) * 4.448222 * Units.Force_N
		end
	end
	local function SimulationLoop()
		while task.wait(SimulationRate) do
			local deltaTime = tick()-last
			last = tick()

			-- Disable if the wheel cannot physically collide
			if (data.FlexCollision and data.FlexCollision.CanCollide == false) or (data.Object.CanCollide == false and data.FlexCollision == nil and data.CollisionModel == nil) then
				return
			end

			local stress = 0
			local count = 0

			local rotSpeed = math.abs(data.Object.RotVelocity:Dot(data.SmokePart.CFrame.RightVector))
			local lateralDiff = math.abs(data.Object.Velocity:Dot(data.Object.CFrame.RightVector))/data.Object.Velocity.Magnitude
			local rotLinearSpeed = rotSpeed*(data.Object.Size.Y/2)
			local speed = VelocityValue.Value.Magnitude
			local diff = math.abs(rotLinearSpeed-speed)

			local ray = nil -- Assuming they are the same material
			local materialType = ""
			local materialChanged = false

			-- Rays
			-- Two rays, one for the inside and outside of the wheel for camber support.
			if _WheelTune.TireWearOn or _WheelTune.SmokeEffects == "Local" or _WheelTune.SmokeEffects == "All" then
				local rayI = workspace:Raycast(
					data.Object.Position,
					((data.Arm.CFrame * CFrame.new(-data.Object.Size.X/2, -data.Object.Size.Y/2, 0)).Position - data.Object.Position) * 1.1,
					rayParams)
				local rayO = workspace:Raycast(
					data.Object.Position,
					((data.Arm.CFrame * CFrame.new(data.Object.Size.X/2, -data.Object.Size.Y/2, 0)).Position - data.Object.Position) * 1.1,
					rayParams)
				ray = rayI or rayO
				if ray then
					-- Run a loop through the materialSort table. Set materialType and break when a material is found.
					for name, values in next, materialSort do
						if table.find(values, ray.Material.Name) then
							materialType = name
							break
						end
					end
				end
				data.Ray = ray
				if data.Material ~= materialType then
					materialChanged = true
				end
				data.Material = materialType
			end

			-- Slip
			local vstress = (math.max(ray and diff/20 or 0, ray and materialType~="road" and materialType ~= "" and rotSpeed/50 or 0) + (front and _WheelTune.FSpeedStress or _WheelTune.RSpeedStress)*(rotLinearSpeed/(Units.Velocity_mdivs/3.6)/100))
			if ray and materialType ~= "road" and materialType ~= "" then
				local random = Random.new():NextNumber() * speed
				if random > 20 then
					data.Object.Velocity += Vector3.new(0, math.random(0, _WheelTune.DirtBumps), 0) * math.min(speed/100, 5)
				end
			end
			if vstress > 0.05 * (60/(1/deltaTime)) and vstress > data.Stress then
				data.Stress = math.min(data.Stress + 0.05 * (60/(1/deltaTime)), math.min(vstress, 1))
			else
				data.Stress = math.max(data.Stress - 0.05 * (60/(1/deltaTime)), vstress)
			end

			-- Update dirt
			if materialType ~= "road" and materialType ~= "" then
				data.Dirt = 1
			else
				data.Dirt = math.max(data.Dirt - (front and _WheelTune.FDirtFadeRate or _WheelTune.RDirtFadeRate) * (rotSpeed/100) * (60/(1/deltaTime)), 0)
			end

			-- Apply friction
			local materialHeatFactor = materialType ~= "road" and .01 or 1
			local currentPhysicalProperties = data.Object.CurrentPhysicalProperties
			local coldLoss = 0
			local heatLoss = 0
			if _WheelTune.TireWearOn then
				if front then
					data.Temperature = math.clamp(data.Temperature + (data.Stress*(_WheelTune.FHeating+_WheelTune.FCooling)*materialHeatFactor - _WheelTune.FCooling) * (60/(1/deltaTime)), _WheelTune.AmbientTemp, _WheelTune.FMaxTemp)
				else
					data.Temperature = math.clamp(data.Temperature + (data.Stress*(_WheelTune.RHeating+_WheelTune.RCooling)*materialHeatFactor - _WheelTune.RCooling) * (60/(1/deltaTime)), _WheelTune.AmbientTemp, _WheelTune.RMaxTemp)
				end
				coldLoss = front and
					(_WheelTune.FColdStats[1] * (1/(math.exp(_WheelTune.FColdStats[2]*(data.Temperature/100-_WheelTune.FColdStats[3]))+1))) or
					(_WheelTune.RColdStats[1] * (1/(math.exp(_WheelTune.RColdStats[2]*(data.Temperature/100-_WheelTune.RColdStats[3]))+1)))
				heatLoss = front and
					(_WheelTune.FHotStats[1] * (-(1/(math.exp(_WheelTune.FHotStats[2]*(data.Temperature/100-_WheelTune.FHotStats[3]))+1))+1)) or
					(_WheelTune.RHotStats[1] * (-(1/(math.exp(_WheelTune.RHotStats[2]*(data.Temperature/100-_WheelTune.RHotStats[3]))+1))+1))
			end
			local density = math.round((((_Tune.FWheelWeight-(_WheelTune.TireFlex and _WheelTune.FTireWeight or 0))*Units.Mass_kg)/(math.pi*(data.Object.Size.Y/2)^2*(data.Object.Size.X)))*1000)/1000
			local friction = math.round(math.clamp(_WheelTune.FMaxFriction - coldLoss - heatLoss - data.Dirt*_WheelTune.FDirtLoss, 0, 2)*1000)/1000
			if not front then
				density = math.round((((_Tune.RWheelWeight-(_WheelTune.TireFlex and _WheelTune.RTireWeight or 0))*Units.Mass_kg)/(math.pi*(data.Object.Size.Y/2)^2*(data.Object.Size.X)))*1000)/1000
				friction = math.round(math.clamp(_WheelTune.RMaxFriction - coldLoss - heatLoss - data.Dirt*_WheelTune.RDirtLoss, 0, 2)*1000)/1000
			end
			if math.round(currentPhysicalProperties.Friction*1000)/1000 ~= friction or math.round(currentPhysicalProperties.Density*1000)/1000 ~= density then
				data.Object.CustomPhysicalProperties = PhysicalProperties.new(
					density,
					friction,
					currentPhysicalProperties.Elasticity,
					currentPhysicalProperties.FrictionWeight,
					currentPhysicalProperties.ElasticityWeight
				)
			end
			if ray and materialType == "sand" then
				data.Object:ApplyImpulseAtPosition(-data.Object.Velocity*_WheelTune.SandForce*deltaTime, data.Object.Position)
			end

			-- Update UI
			if script.Parent.Visible and script.Parent.Tires.Visible then
				local tire = data.tireUI
				if tire then
					local front = string.find(tire.Name, "F")
					tire.First.Second.Size = UDim2.new(1, 0, math.clamp(1-(data.Temperature-40)/((front and _WheelTune.FMaxTemp or _WheelTune.RMaxTemp)-40),0,1), 0)
					tire.BackgroundColor3 = Color3.fromHSV(0, data.Stress, 1)
					tire.BackgroundTransparency = .8 - .3 * data.Stress
				end
			end
			data.Object:SetAttribute("Temperature", data.Temperature)
			data.Object:SetAttribute("Dirt", data.Dirt)

			-- Update haptic values
			stress += data.Stress
			count += 1

			-- Tire flex
			if _WheelTune.TireFlex and data.FlexCollision then
				local flexDensity = math.round((((string.find(name, "F") and _WheelTune.FTireWeight or _WheelTune.RTireWeight)*Units.Mass_kg)/(math.pi*(data.Object.Size.Y/2)^2*(data.Object.Size.X)))*1000)/1000
				local flexCurrentPhysicalProperties = data.FlexCollision.CurrentPhysicalProperties
				if math.round(flexCurrentPhysicalProperties.Density*1000)/1000 ~= flexDensity or
					currentPhysicalProperties.Friction ~= flexCurrentPhysicalProperties.Friction or
					currentPhysicalProperties.Elasticity ~= flexCurrentPhysicalProperties.Elasticity or
					currentPhysicalProperties.FrictionWeight ~= flexCurrentPhysicalProperties.FrictionWeight or
					currentPhysicalProperties.ElasticityWeight ~= flexCurrentPhysicalProperties.ElasticityWeight then

					data.FlexCollision.CustomPhysicalProperties = PhysicalProperties.new(
						flexDensity,
						currentPhysicalProperties.Friction,
						currentPhysicalProperties.Elasticity,
						currentPhysicalProperties.FrictionWeight,
						currentPhysicalProperties.ElasticityWeight
					)
				end
				if data.FlexCollisionGravComp then
					data.FlexCollisionGravComp.Force = Vector3.new(0, _Tune.GravComp > 0 and data.FlexCollision.Mass*(workspace.Gravity-_Tune.GravComp) or 0, 0)
				end
				if data.ObjectGravComp then
					data.ObjectGravComp.Force = Vector3.new(0, _Tune.GravComp > 0 and data.Object.Mass*(workspace.Gravity-_Tune.GravComp) or 0, 0)
				end
			end

			-- Smoke
			if _WheelTune.SmokeEffects == "Local" or _WheelTune.SmokeEffects == "All" then
				-- Set sounds and smoke
				local currentSound = data.CurrentSound
				local currentSmoke = data.CurrentSmoke
				if materialChanged then
					if currentSound then
						currentSound.Playing = false
					end
					if currentSmoke then
						currentSmoke.Enabled = false
					end
					currentSound = data.Sounds[materialType]
					currentSmoke = data.Smokes[materialType]
					data.CurrentSound = currentSound
					data.CurrentSmoke = currentSmoke
				end

				-- Set Trails
				local smokeStress = math.clamp(data.Stress - _WheelTune.SmokeStressOffset/100, 0, 1)
				local trailStress = math.floor(smokeStress^3 * _WheelTune.TireMarkTransparencyLevels)
				if data.Trails[materialType] then
					for i = 1, (trailStress > data.TrailStress and trailStress or data.TrailStress) do
						data.Trails[materialType][i].Enabled = trailStress >= i
					end
				end
				if materialChanged and data.CurrentTrail then
					for i = 1, _WheelTune.TireMarkTransparencyLevels do
						data.CurrentTrail[i].Enabled = false
					end
				end
				data.CurrentTrail = data.Trails[materialType]
				data.TrailStress = trailStress

				-- Run effects
				if currentSound then
					-- Sound
					if not currentSound.Playing then
						currentSound.TimePosition = Random.new():NextNumber() * currentSound.TimeLength --avoid overlapping sounds
					end
					currentSound.Playing = true
					currentSound.Volume = smokeStress ^ 3 * 1.4
					currentSound.PlaybackSpeed = 1.6 - 0.5 * smokeStress
					local tremolo = currentSound:GetAttribute("HasTremolo") and currentSound.TremoloSoundEffect
					if tremolo then
						tremolo.Depth = lateralDiff
					end
				end
				if currentSmoke then
					-- Smoke
					currentSmoke.Enabled = true
					currentSmoke.Rate = smokeStress^10 * 100
				end
				local compression = 1
				if data.Collision then
					compression = math.clamp((data.Object.Position-data.Collision.Position).Magnitude/.1, 0, 1)
				end
				local trailAttachmentPart = data.TrailAttachmentPart
				if trailAttachmentPart and smokeStress > 0.05 and data.Object.Velocity.Magnitude > 10 then
					trailAttachmentPart.TrailAttachment0.Position = Vector3.new(data.Object.Size.X/2-.2 + .1*compression, .02, 0)
					trailAttachmentPart.TrailAttachment1.Position = Vector3.new(-data.Object.Size.X/2+.2 - .1*compression, .02, 0)
					trailAttachmentPart.AlignOrientation.CFrame = CFrame.lookAt(data.SmokePart.Position, (data.SmokePart.CFrame * CFrame.new(0, 0, -5 + math.min(data.Object.Velocity.Magnitude, 5))).Position+data.Object.Velocity, ray and ray.Normal or data.SmokePart.CFrame.UpVector)
				end
			end

			-- Haptics
			if isVibrationSupported and ((_WheelTune.Haptics == "Controller" or _WheelTune.Haptics == "Both" and string.find(lastInput.Name, "Gamepad")) or (_WheelTune.Haptics == "Mobile" or _WheelTune.Haptics == "Both" and lastInput == Enum.UserInputType.Touch)) then
				stress /= count
				stress = math.clamp(stress/2, 0, .5)
				HapticService:SetMotor(Enum.UserInputType.Gamepad1, Enum.VibrationMotor.Small, stress)
			end
		end
	end
	task.defer(SimulationLoop)
	RunService.Heartbeat:Connect(TireFlexPhysics)
end

--Everything else
if _WheelTune.SmokeEffects == "All" then
	local rate = _WheelTune.SmokePollingRate
	local lastTick = tick()
	local function SlowRuntimeLoop()
		while task.wait(rate) do
			for name, data in next, WheelData do
				if not dtTable[name] then continue end
				local ray = data.Ray
				local materialType = data.Material
				local smokeStress = math.clamp(data.Stress - _WheelTune.SmokeStressOffset/100, 0, 1)
				local rayNormal = ray and ray.Normal or Vector3.zero
				local packet = buffer.create(8)
				buffer.writeu8(packet, 0, data.ID)
				buffer.writeu8(packet, 1, materialID[materialType] or 0)
				buffer.writeu8(packet, 2, math.clamp(math.round(smokeStress*255), 0, 255))
				buffer.writei8(packet, 3, math.round(rayNormal.X*127))
				buffer.writei8(packet, 4, math.round(rayNormal.Y*127))
				buffer.writei8(packet, 5, math.round(rayNormal.Z*127))
				buffer.writeu16(packet, 6, math.clamp(math.round(dtTable[name]*65535), 0, 65535))
				event:FireServer(packet)
			end
		end
	end
	task.defer(SlowRuntimeLoop)
end


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

local Tune = {}

-- It is recommended that you go to: https://www.desmos.com/calculator/lhxwlpppyh
-- to tune your vehicle's tires.

-- The default configuration is of a soft-compound sports tire.

--[[Misc]]
	Tune.SaveHeatData		= true				-- Whether to save heat data when leaving the car
	Tune.TireWearOn			= true				-- False sets friction to maximum
	Tune.Haptics			= "Controller"		-- Tire slippage will activate the small motor ("Controller", "Mobile", "Both", "Off")
	Tune.AmbientTemp		= workspace:GetAttribute("GlobalTemperature") or 22

	Tune.DirtBumps			= 20				-- Aggresiveness of artificial bumps in dirt-, sand-, and snow-type surfaces (0 for none)
	Tune.SandForce			= 10					-- Sand force, more means going over sand slows down the car more

--[[Aesthetics]]
	Tune.SmokeEffects				= "All"		-- Turns smoke effects on or off, "All" for viewable by everyone, "Local" for only viewable by you, "Off" for none.
	Tune.SmokeStressOffset			= 5			-- Offsets the stress required to activate smoke-related effects (in percent, higher means less smoke).
	Tune.SmokePollingRate			= 1/60		-- "All" setting only. This changes how often the server gets data to run the smoke effects. Higher = more delay.
	Tune.TireMarkTransparencyLevels	= 7			-- How many levels of transparency there are (0 for no marks)
	Tune.TireMarkTransparency		= .95		-- The transparency of the tire marks

--[[Friction]]
	Tune.FMinTemp			= 8					-- Minimum temperature of the front tires in celcius
	Tune.FMaxTemp			= 100				-- Maximum temperature of the front tires in celcius
	Tune.FMaxFriction		= 1.8				-- Maximum friction of the front tires
	Tune.FColdStats			= {.38, 11.7, .32}	-- Loss, Sharpness, Offset
	Tune.FHotStats			= {.96, 18.3, 1.05}	-- Loss, Sharpness, Offset
	Tune.FDirtLoss			= .1				-- Friction loss from dirty tires
	Tune.FDirtFadeRate		= .001				-- The rate that dirt fades
	Tune.FHeating			= .001				-- Heat gain from maximum slippage
	Tune.FCooling			= .002				-- Heat loss at no slippage
	Tune.FSpeedStress		= .04				-- Stress generated at 100 km/h

	Tune.RMinTemp			= 8					-- Minimum temperature of the rear tires in celcius
	Tune.RMaxTemp			= 100				-- Maximum temperature of the rear tires in celcius
	Tune.RMaxFriction		= 2.0				-- Maximum friction of the rear tires
	Tune.RColdStats			= {.38, 11.7, .32}	-- Loss, Sharpness, Offset
	Tune.RHotStats			= {.96, 18.3, 1.05}	-- Loss, Sharpness, Offset
	Tune.RDirtLoss			= .1				-- Friction loss from dirty tires
	Tune.RDirtFadeRate		= .001				-- The rate that dirt fades
	Tune.RHeating			= .001				-- Heat gain from maximum slippage
	Tune.RCooling			= .002				-- Heat loss at no slippage
	Tune.RSpeedStress		= .04				-- Stress generated at 100 km/h

--[[Tire Flex]]
	Tune.TireFlex			= false				-- Uses a separate collision to simulate tire flex
	Tune.ShowTF				= false				-- Debug tire flex

	Tune.FPressureForce		= 18				-- Pressure force (no unit)
	Tune.FTireThickness		= 50				-- Tire sidewall thickness in milimeters
	Tune.FTireWeight		= 9					-- Tire weight in kg

	Tune.RPressureForce		= 18				-- Pressure force (no unit)
	Tune.RTireThickness		= 50				-- Tire sidewall thickness in milimeters
	Tune.RTireWeight		= 9					-- Tire weight in kg

return Tune


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

-- All units derived from: https://create.roblox.com/docs/physics/units
-- Converting from the real life unit to the Roblox unit
-- Equations to convert from the real life measurement to the Roblox unit are given.
local Units = {}

-- Mass in kilograms (kg)
Units.Mass_kg = 1/21.952		-- [Mass in kilograms] * [Units.Mass_kg] = [Roblox mass]

-- Length in milimeters (mm)
Units.Length_mm = 280		-- [Length in meters] * [1/(Units.Length_mm/1000)] = [Roblox studs]

-- Force in newtons (N)
Units.Force_N = Units.Mass_kg * (1/(Units.Length_mm/1000))		-- [Force in newtons] * [Units.Force_N] = [Roblox force]

-- Stiffness in newtons per meter (N/m)
Units.Stiffness_Ndivm = Units.Mass_kg		-- [Stiffness in N/m] * [Units.Stiffness_Ndivm] = [Roblox stiffness]

-- Damping in newton-seconds per meter (N s/m)
Units.Damping_N_sdivm = Units.Mass_kg		-- [Damping in N s/m] * [Units.Damping_N_sdivm] = [Roblox damping]

-- Velocity in meters per second (m/s)
Units.Velocity_mdivs = 1/(Units.Length_mm/1000)			-- [Velocity in m/s] * [Units.Velocity_mdivs] = [Roblox velocity]

-- Torque in newton-meters (Nm)
Units.Torque_nm = Units.Force_N/(Units.Length_mm/1000)	-- [Torque in Nm] * [Units.Torque_nm] = [Roblox torque]

return Units


--Wraps the Nexus VR Character Model main module for loading.
--When required with an id, the main module's source isn't
--included, which makes the client see an empty script.
--!strict

local REQUIRED_LIBRARIES = {
    Enigma = "enigma",
    NexusAppendage = "nexus-appendage",
    NexusBufferedReplication = "nexus-buffered-replication",
    NexusButton = "nexus-button",
    NexusInstance = "nexus-instance",
    NexusVRCore = "nexus-vr-core",
}

--Copy the dependencies if it is a Wally package.
--This is meant to provide backward compatibility with the expected final module location.
local SourcePackages = script.Parent
if SourcePackages then
    local TargetPackages = script:WaitForChild("NexusVRCharacterModel"):WaitForChild("Packages")
    if not TargetPackages:FindFirstChild("_Index") then
        local Index = Instance.new("Folder")
        Index.Name = "_Index"
        Index.Parent = TargetPackages

        for PackageName, WallyPackageName in REQUIRED_LIBRARIES do
            local SourceReference = SourcePackages:FindFirstChild(PackageName)
            local SourcePackage = SourcePackages:FindFirstChild(WallyPackageName, true)
            local TargetPackage = TargetPackages:FindFirstChild(PackageName)
            if SourceReference and SourcePackage and TargetPackage then
                SourceReference:Clone().Parent = TargetPackages
                SourcePackage.Parent:Clone().Parent = Index
                TargetPackage:Destroy()
            end
        end
    end
end

--Load the module.
return require(script:WaitForChild("NexusVRCharacterModel"))


--Main module for creating the usable API.
--!strict

local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")

return function()
    local NexusVRCharacterModel = script.Parent
    local TypedEvent = require(NexusVRCharacterModel:WaitForChild("Packages"):WaitForChild("NexusInstance")).TypedEvent
    local API = {} :: any
    API.Registered = TypedEvent.new()

    --[[
    Stores an API that can be referenced. If the API is already stored,
    an error will be thrown.
    --]]
    function API:Register(ApiName: string, Api: any): ()
        if self[ApiName] ~= nil then
            error(`API already registered: {ApiName}`)
        end
        self[ApiName] = Api
        self.Registered:Fire(ApiName)
    end

    --[[
    Waits for an API to be registered and returns the API. If it was
    already registered, it returns the API without waiting. Similar
    to instances, this would be treated like WaitForChild where the
    usage is optional instead of indexing (ex: API:WaitFor("MyApi")
    vs API.MyApi) as long as the consequences of an API not
    being registered are accepted.
    --]]
    function API:WaitFor(ApiName: string): any
        while not self[ApiName] do
            self.Registered:Wait()
        end
        return self[ApiName]
    end

    --[[
    Invokes a callback when an API is registered with a given
    name. If it is already registered, the callback will run
    asynchronously. This is intended for setting up an API
    call without blocking for WaitFor.
    --]]
    function API:OnRegistered(ApiName: string, RegisteredCallback: (any) -> ()): ()
        --Run the callback immediately if the API is loaded.
        if self[ApiName] then
            task.spawn(function()
                RegisteredCallback(self[ApiName])
            end)
            return
        end

        --Connect the registered event.
        self.Registered:Connect(function(RegisteredFunctionName)
            if ApiName ~= RegisteredFunctionName then return end
            RegisteredCallback(self[ApiName])
        end)
    end

    --Create the client API.
    --Done in a task to resolve recurisve requiring.
    if RunService:IsClient() then
        task.defer(function()
            --Create the camera API.
            local CameraService = require(NexusVRCharacterModel:WaitForChild("State"):WaitForChild("CameraService")).GetInstance()
            local CameraAPI = {}
            function CameraAPI:SetActiveCamera(Name: string): ()
                CameraService:SetActiveCamera(Name)
            end
            function CameraAPI:GetActiveCamera(): string
                return CameraService.ActiveCamera
            end
            API:Register("Camera", CameraAPI)

            --Create the controller API.
            local ActiveControllers = {}
            local ControlService = require(NexusVRCharacterModel:WaitForChild("State"):WaitForChild("ControlService")).GetInstance()
            local ControllerAPI = {}
            function ControllerAPI:SetActiveController(Name: string): ()
                ControlService:SetActiveController(Name)
            end
            function ControllerAPI:GetActiveController(): (string)
                return ControlService.ActiveController
            end
            function ControllerAPI:SetControllerInputEnabled(Hand: Enum.UserCFrame, Enabled: boolean): ()
                if Hand ~= Enum.UserCFrame.LeftHand and Hand ~= Enum.UserCFrame.RightHand then
                    error(`The following UserCFrame is invalid and can't be disabled: {Hand}`)
                end
                ActiveControllers[Hand] = (Enabled ~= false)
            end
            function ControllerAPI:EnableControllerInput(Hand: Enum.UserCFrame): ()
                self:SetControllerInputEnabled(Hand, true)
            end
            function ControllerAPI:DisableControllerInput(Hand: Enum.UserCFrame): ()
                self:SetControllerInputEnabled(Hand, false)
            end
            function ControllerAPI:IsControllerInputEnabled(Hand: Enum.UserCFrame): boolean
                if Hand ~= Enum.UserCFrame.LeftHand and Hand ~= Enum.UserCFrame.RightHand then
                    error(`The following UserCFrame is invalid and can't be disabled: {Hand}`)
                end
                return ActiveControllers[Hand] ~= false
            end
            API:Register("Controller", ControllerAPI)

            --Create the input API.
            local VRInputService = require(NexusVRCharacterModel:WaitForChild("State"):WaitForChild("VRInputService")).GetInstance()
            local InputAPI = {}
            InputAPI.Recentered = VRInputService.Recentered
            InputAPI.EyeLevelSet = VRInputService.EyeLevelSet
            function InputAPI:Recenter(): ()
                VRInputService:Recenter()
            end
            function InputAPI:SetEyeLevel(): ()
                VRInputService:SetEyeLevel()
            end
            API:Register("Input", InputAPI)

            --Create the Menu API.
            --The Menu API does not work outside of VR.
            --Release 454 and later has/had a bug that made VREnabled false on start. This mitigates that now and in the future if VR loads dynamically.
            local MenuAPI = {} :: any
            local function GetMainMenu(): any
                if not MenuAPI.Enabled then
                    error("Menu API is not enabled for non-VR players. Check Api.Menu.Enabled before calling.")
                end
                return require(NexusVRCharacterModel:WaitForChild("UI"):WaitForChild("MainMenu")).GetInstance()
            end
            if UserInputService.VREnabled then
                MenuAPI.Enabled = true
            else
                MenuAPI.Enabled = false
                UserInputService:GetPropertyChangedSignal("VREnabled"):Connect(function()
                    MenuAPI.Enabled = UserInputService.VREnabled
                end)
            end

            MenuAPI.CreateView = function(_, ...)
                return GetMainMenu():CreateView(...)
            end
            MenuAPI.IsOpen = function()
                return GetMainMenu().ScreenGui.Enabled
            end
            MenuAPI.Open = function(self)
                if self:IsOpen() then return end
                GetMainMenu():Toggle()
            end
            MenuAPI.Close = function(self)
                if not self:IsOpen() then return end
                GetMainMenu():Toggle()
            end
            API:Register("Menu", MenuAPI)

            --Create the settings API.
            local SettingsService = require(NexusVRCharacterModel:WaitForChild("State"):WaitForChild("Settings")).GetInstance()
            local SettingsAPI = {}
            function SettingsAPI:GetSetting(Setting: string): any
                return SettingsService:GetSetting(Setting)
            end
            function SettingsAPI:SetSetting(Setting: string, Value: any): ()
                SettingsService:SetSetting(Setting, Value)
            end
            function SettingsAPI:GetSettingsChangedSignal(Setting: string)
                return SettingsService:GetSettingsChangedSignal(Setting)
            end
            API:Register("Settings", SettingsAPI)
        end)
    end

    --Return the APIs.
    return API
end


 --Stores information about an appendage, such as an arm or a leg.
--!strict

local NexusAppendage = require(script.Parent.Parent:WaitForChild("Packages"):WaitForChild("NexusAppendage"))

local Limb = NexusAppendage.Limb

local Appendage = {}
Appendage.__index = Appendage
setmetatable(Appendage, Limb)

export type Appendage = {
    UpperLimb: BasePart,
    LowerLimb: BasePart,
    LimbEnd: BasePart,
    StartAttachment: string,
    LimbJointAttachment: string,
    LimbEndAttachment: string,
    LimbHoldAttachment: string,
    InvertBendDirection: boolean,
    PreventDisconnection: boolean,
} & typeof(setmetatable({}, Appendage)) & NexusAppendage.Limb

--[[
Creates an appendage.
--]]
function Appendage.new(UpperLimb: BasePart, LowerLimb: BasePart, LimbEnd: BasePart, StartAttachment: string, LimbJointAttachment: string, LimbEndAttachment: string, LimbHoldAttachment: string, PreventDisconnection: boolean?): Appendage
    local self = setmetatable(Limb.new() :: any, Appendage)
    self.UpperLimb = UpperLimb
    self.LowerLimb = LowerLimb
    self.LimbEnd = LimbEnd
    self.StartAttachment = StartAttachment
    self.LimbJointAttachment = LimbJointAttachment
    self.LimbEndAttachment = LimbEndAttachment
    self.LimbHoldAttachment = LimbHoldAttachment
    self.InvertBendDirection = false
    self.PreventDisconnection = PreventDisconnection or false
    return setmetatable(self, Appendage)
end

--[[
Attempts to solve a joint. This uses
the "naive" approach for inverse kinematics.
--]]
function Appendage.SolveJoint(self: Appendage, OriginCFrame: CFrame, TargetPosition: Vector3, Length1: number, Length2: number): (CFrame, number, number)
    local LocalizedPosition = OriginCFrame:PointToObjectSpace(TargetPosition)
    local LocalizedUnit = LocalizedPosition.Unit
    local Hypotenuse = LocalizedPosition.Magnitude

    --Get the axis and correct it if it is 0.
    local Axis = Vector3.new(0, 0, -1):Cross(LocalizedUnit)
    if Axis == Vector3.new(0, 0, 0) then
        if LocalizedPosition.Z < 0 then
            Axis = Vector3.new(0, 0, 0.001)
        else
            Axis = Vector3.new(0, 0, -0.001)
        end
    end

    --Calculate and return the angles.
    local PlaneRotation = math.acos(-LocalizedUnit.Z)
    local PlaneCFrame = OriginCFrame * CFrame.fromAxisAngle(Axis, PlaneRotation)
    if Hypotenuse < math.max(Length2, Length1) - math.min(Length2, Length1) then
        local ShoulderAngle, ElbowAngle = -math.pi / 2, math.pi
        if self.PreventDisconnection then
            return PlaneCFrame, ShoulderAngle, ElbowAngle
        else
            return PlaneCFrame * CFrame.new(0, 0, math.max(Length2, Length1) - math.min(Length2, Length1) - Hypotenuse), ShoulderAngle, ElbowAngle
        end
    elseif Hypotenuse > Length1 + Length2 then
        local ShoulderAngle, ElbowAngle = math.pi / 2, 0
        if self.PreventDisconnection then
            return PlaneCFrame, ShoulderAngle, ElbowAngle
        else
            return PlaneCFrame * CFrame.new(0, 0, Length1 + Length2 - Hypotenuse), ShoulderAngle, ElbowAngle
        end
    else
        local Angle1 = -math.acos((-(Length2 * Length2) + (Length1 * Length1) + (Hypotenuse * Hypotenuse)) / (2 * Length1 * Hypotenuse))
        local Angle2 = math.acos(((Length2  * Length2) - (Length1 * Length1) + (Hypotenuse * Hypotenuse)) / (2 * Length2 * Hypotenuse))
        if self.InvertBendDirection then
            Angle1 = -Angle1
            Angle2 = -Angle2
        end
        return PlaneCFrame , Angle1 + math.pi / 2, Angle2 - Angle1
    end
end

--[[
Returns the rotation offset relative to the Y axis
to an end CFrame.
--]]
function Appendage.RotationTo(self: Appendage, StartCFrame: CFrame, EndCFrame: CFrame): CFrame
	local Offset = (StartCFrame:Inverse() * EndCFrame).Position
	return CFrame.Angles(math.atan2(Offset.Z, Offset.Y), 0, -math.atan2(Offset.X, Offset.Y))
end

--[[
Returns the CFrames of the appendage for
the starting and holding CFrames. The implementation
works, but could be improved.
--]]
function Appendage.GetAppendageCFrames(self: Appendage, StartCFrame: CFrame, HoldCFrame: CFrame): (CFrame, CFrame, CFrame)
    --Get the attachment CFrames.
    local LimbHoldCFrame = self:GetAttachmentCFrame(self.LimbEnd, self.LimbHoldAttachment)
    local LimbEndCFrame = self:GetAttachmentCFrame(self.LimbEnd, self.LimbEndAttachment)
    local UpperLimbStartCFrame = self:GetAttachmentCFrame(self.UpperLimb, self.StartAttachment)
    local UpperLimbJointCFrame = self:GetAttachmentCFrame(self.UpperLimb, self.LimbJointAttachment)
    local LowerLimbJointCFrame = self:GetAttachmentCFrame(self.LowerLimb, self.LimbJointAttachment)
    local LowerLimbEndCFrame = self:GetAttachmentCFrame(self.LowerLimb, self.LimbEndAttachment)

    --Calculate the appendage lengths.
    local UpperLimbLength = (UpperLimbStartCFrame.Position - UpperLimbJointCFrame.Position).Magnitude
    local LowerLimbLength = (LowerLimbJointCFrame.Position - LowerLimbEndCFrame.Position).Magnitude

    --Calculate the end point of the limb.
    local AppendageEndJointCFrame = HoldCFrame * LimbHoldCFrame:Inverse() * LimbEndCFrame

    --Solve the join.
    local PlaneCFrame,UpperAngle,CenterAngle = self:SolveJoint(StartCFrame, AppendageEndJointCFrame.Position, UpperLimbLength, LowerLimbLength)

    --Calculate the CFrame of the limb join before and after the center angle.
    local JointUpperCFrame = PlaneCFrame * CFrame.Angles(UpperAngle, 0, 0) * CFrame.new(0, -UpperLimbLength, 0)
    local JointLowerCFrame = JointUpperCFrame * CFrame.Angles(CenterAngle, 0, 0)

    --Calculate the part CFrames.
    --The appendage end is not calculated with hold CFrame directly since it can ignore PreventDisconnection = true.
    local UpperLimbCFrame = JointUpperCFrame * self:RotationTo(UpperLimbJointCFrame, UpperLimbStartCFrame):Inverse() * UpperLimbJointCFrame:Inverse()
    local LowerLimbCFrame = JointLowerCFrame * self:RotationTo(LowerLimbEndCFrame, LowerLimbJointCFrame):Inverse() * LowerLimbJointCFrame:Inverse()
    local AppendageEndCFrame = CFrame.new((LowerLimbCFrame * LowerLimbEndCFrame).Position) * (CFrame.new(-AppendageEndJointCFrame.Position) * AppendageEndJointCFrame) * LimbEndCFrame:Inverse()

    --Return the part CFrames.
    return UpperLimbCFrame, LowerLimbCFrame, AppendageEndCFrame
end

return Appendage


--Common camera functionality.
--!strict

local Workspace = game:GetService("Workspace")
local VRService = game:GetService("VRService")

local NexusVRCharacterModel = script.Parent.Parent.Parent
local Settings = require(NexusVRCharacterModel:WaitForChild("State"):WaitForChild("Settings")).GetInstance()

local CommonCamera = {}
CommonCamera.__index = CommonCamera

export type CommonCamera = typeof(setmetatable({}, CommonCamera))

--[[
Creates the common camera.
--]]
function CommonCamera.new(): CommonCamera
    return setmetatable({}, CommonCamera)
end

--[[
Enables the camera.
--]]
function CommonCamera.Enable(self: CommonCamera): ()

end

--[[
Disables the camera.
--]]
function CommonCamera.Disable(self: CommonCamera): ()

end

--[[
Updates the camera.
--]]
function CommonCamera.UpdateCamera(self: CommonCamera, HeadsetCFrameWorld: CFrame): ()

end

--[[
Sets the camera CFrame.
--]]
function CommonCamera.SetCFrame(self: CommonCamera, HeadsetCFrameWorld: CFrame): ()
    --Lock HeadLocked to false.
    --The default behavior is to do it for backwards compatibility with 2.6.0 and earlier.
    local Camera = Workspace.CurrentCamera
    if Settings:GetSetting("Camera.DisableHeadLocked") ~= false then
        Camera.HeadLocked = false
    end

    --Set the camera CFrame.
    local TargetCFrame = HeadsetCFrameWorld
    if Camera.HeadLocked then
        local HeadCFrame = VRService:GetUserCFrame(Enum.UserCFrame.Head)
        TargetCFrame = HeadsetCFrameWorld * (CFrame.new(HeadCFrame.Position * (Workspace.CurrentCamera.HeadScale - 1)) * HeadCFrame):Inverse()
        Camera.VRTiltAndRollEnabled = true
    end
    Camera.CameraType = Enum.CameraType.Scriptable
    Camera.CFrame = TargetCFrame
    Camera.Focus = TargetCFrame
end

return CommonCamera


--Default camera that follows the character.
--!strict

local BUMP_DEFAULT_TRANSPARENCY_WORKAROUND = true
local HIDDEN_ACCESSORIES = {
    [Enum.AccessoryType.Hat] = true;
    [Enum.AccessoryType.Hair] = true;
    [Enum.AccessoryType.Face] = true;
    [Enum.AccessoryType.Eyebrow] = true;
    [Enum.AccessoryType.Eyelash] = true;
}

local Players = game:GetService("Players")
local VRService = game:GetService("VRService")

local NexusVRCharacterModel = script.Parent.Parent.Parent
local CommonCamera = require(NexusVRCharacterModel:WaitForChild("Character"):WaitForChild("Camera"):WaitForChild("CommonCamera"))
local Settings = require(NexusVRCharacterModel:WaitForChild("State"):WaitForChild("Settings")).GetInstance()

local DefaultCamera = {}
DefaultCamera.__index = DefaultCamera
setmetatable(DefaultCamera, CommonCamera)

export type DefaultCamera = {
    TransparencyEvents: {RBXScriptConnection}?,
} & typeof(setmetatable({}, DefaultCamera)) & CommonCamera.CommonCamera

--[[
Returns true if the provided part should be hidden in first person.
--]]
function DefaultCamera.ShouldHidePart(Part: BasePart): boolean
    local Parent: Instance? = Part.Parent
    if Parent then
        if Parent:IsA("Accessory") then
            local AccessoryType = Parent.AccessoryType
            return HIDDEN_ACCESSORIES[AccessoryType] or false
        elseif Parent:IsA("Model") then
            return false
        else
            return not Parent:IsA("Tool")
        end
    end

    if Part:FindFirstChildWhichIsA("WrapLayer") then
        return false
    end

    return true
end

--[[
Creates a default camera object.
--]]
function DefaultCamera.new(): DefaultCamera
    return setmetatable(CommonCamera.new() :: any, DefaultCamera) :: DefaultCamera
end

--[[
Enables the camera.
--]]
function DefaultCamera.Enable(self: DefaultCamera): ()
    local TransparencyEvents = {}
    self.TransparencyEvents = TransparencyEvents

    if Players.LocalPlayer.Character then
        --Connect children being added.
        local Transparency = Settings:GetSetting("Appearance.LocalCharacterTransparency")
        if BUMP_DEFAULT_TRANSPARENCY_WORKAROUND then
            if Transparency == 0.5 then
                Transparency = 0.501
            elseif Transparency < 0.5 then
                warn("Values of <0.5 with Appearance.LocalCharacterTransparency are currently known to cause black screen issues. This will hopefully be resolved by Roblox in a future update: https://devforum.roblox.com/t/vr-screen-becomes-black-due-to-non-transparent-character/2215099")
            end
        end
        table.insert(TransparencyEvents, Players.LocalPlayer.Character.DescendantAdded:Connect(function(Part: BasePart)
            if Part:IsA("BasePart") then
                local PartTransparency = Transparency
                if Part:FindFirstAncestorOfClass("Tool") then
                    PartTransparency = 0
                elseif DefaultCamera.ShouldHidePart(Part) then
                    PartTransparency = 1
                end

                Part.LocalTransparencyModifier = PartTransparency
                table.insert(TransparencyEvents, Part:GetPropertyChangedSignal("LocalTransparencyModifier"):Connect(function()
                    Part.LocalTransparencyModifier = PartTransparency
                end))
            end
        end))
        for _, Part in Players.LocalPlayer.Character:GetDescendants() do
            if Part:IsA("BasePart") then
                local PartTransparency = Transparency
                if Part:FindFirstAncestorOfClass("Tool") then
                    PartTransparency = 0
                elseif DefaultCamera.ShouldHidePart(Part) then
                    PartTransparency = 1
                end

                Part.LocalTransparencyModifier = PartTransparency
                table.insert(TransparencyEvents, Part:GetPropertyChangedSignal("LocalTransparencyModifier"):Connect(function()
                    Part.LocalTransparencyModifier = PartTransparency
                end))
            end
        end
    end

    --Connect the character and local transparency changing.
    table.insert(TransparencyEvents, Players.LocalPlayer:GetPropertyChangedSignal("Character"):Connect(function()
        self:Disable()
        self:Enable()
    end))
    table.insert(TransparencyEvents, Settings:GetSettingsChangedSignal("Appearance.LocalCharacterTransparency"):Connect(function()
        self:Disable()
        self:Enable()
    end) :: any)

    --Keep the camera in first person.
    if VRService.AvatarGestures then
        Players.LocalPlayer.CameraMaxZoomDistance = Players.LocalPlayer.CameraMinZoomDistance
    end
end

--[[
Disables the camera.
--]]
function DefaultCamera.Disable(self: DefaultCamera): ()
    --Disconnect the character events.
    if self.TransparencyEvents then
        for _, Event in self.TransparencyEvents do
            Event:Disconnect()
        end
        self.TransparencyEvents = {}
    end

    --Reset the local transparency modifiers.
    if Players.LocalPlayer.Character then
        for _, Part in Players.LocalPlayer.Character:GetDescendants() do
            if Part:IsA("BasePart") then
                Part.LocalTransparencyModifier = 0
            end
        end
    end
end

--[[
Updates the camera.
--]]
function DefaultCamera.UpdateCamera(self: DefaultCamera, HeadsetCFrameWorld: CFrame): ()
    if VRService.AvatarGestures then return end
    self:SetCFrame(HeadsetCFrameWorld)
end

return DefaultCamera


--Third person camera that moves with the player.
--!strict

local THIRD_PERSON_ZOOM = 10

local Players = game:GetService("Players")

local NexusVRCharacterModel = script.Parent.Parent.Parent
local CommonCamera = require(NexusVRCharacterModel:WaitForChild("Character"):WaitForChild("Camera"):WaitForChild("CommonCamera"))

local ThirdPersonTrackCamera = {}
ThirdPersonTrackCamera.__index = ThirdPersonTrackCamera
setmetatable(ThirdPersonTrackCamera, CommonCamera)

export type ThirdPersonTrackCamera = {
    FetchInitialCFrame: boolean?,
    BaseFaceAngleY: number?,
    BaseCFrame: CFrame?,
} & typeof(setmetatable({}, ThirdPersonTrackCamera)) & CommonCamera.CommonCamera

--[[
Creates a third-person camera object.
--]]
function ThirdPersonTrackCamera.new(): ThirdPersonTrackCamera
    return setmetatable(CommonCamera.new() :: any, ThirdPersonTrackCamera) :: ThirdPersonTrackCamera
end

--[[
Enables the camera.
--]]
function ThirdPersonTrackCamera.Enable(self: ThirdPersonTrackCamera): ()
    self.FetchInitialCFrame = true
end

--[[
Disables the camera.
--]]
function ThirdPersonTrackCamera.Disable(self: ThirdPersonTrackCamera): ()
    self.FetchInitialCFrame = nil
end

--[[
Updates the camera.
--]]
function ThirdPersonTrackCamera.UpdateCamera(self: ThirdPersonTrackCamera, HeadsetCFrameWorld: CFrame): ()
    --Set the initial CFrame to use.
    if self.FetchInitialCFrame then
        local BaseFaceAngleY = math.atan2(-HeadsetCFrameWorld.LookVector.X, -HeadsetCFrameWorld.LookVector.Z)
        self.BaseFaceAngleY = BaseFaceAngleY
        self.BaseCFrame = CFrame.new(HeadsetCFrameWorld.Position) * CFrame.Angles(0, BaseFaceAngleY, 0)
        self.FetchInitialCFrame = nil
    end

    --Get the scale.
    local Scale = 1
    local Character = Players.LocalPlayer.Character
    if Character then
        local Humanoid = Character:FindFirstChildOfClass("Humanoid")
        if Humanoid then
            local BodyHeightScale = Humanoid:FindFirstChild("BodyHeightScale")
            if BodyHeightScale then
                Scale = BodyHeightScale.Value
            end
        end
    end

    --Calculate the third person CFrame.
    local BaseCFrame = self.BaseCFrame :: CFrame
    local HeadsetRelative = BaseCFrame:Inverse() * HeadsetCFrameWorld
    local TargetCFrame = BaseCFrame * CFrame.new(0, 0, -THIRD_PERSON_ZOOM * Scale) * CFrame.Angles(0, math.pi, 0) * HeadsetRelative

    --Update the camera.
    self:SetCFrame(TargetCFrame)
end

return ThirdPersonTrackCamera


 --Base class for controlling the local character.
--!strict

local THUMBSTICK_INPUT_START_RADIUS = 0.6
local THUMBSTICK_INPUT_RELEASE_RADIUS = 0.4
local THUMBSTICK_SNAP_ROTATION_ANGLE = math.rad(30) --Roblox's snap rotation is 30 degrees.
local THUMBSTICK_SMOOTH_LOCOMOTION_DEADZONE = 0.2
local THUMBSTICK_MANUAL_SMOOTH_ROTATION_RATE = math.rad(360) --May or may not be accurate for Roblox's player scripts.
local BLUR_TWEEN_INFO = TweenInfo.new(0.25, Enum.EasingStyle.Quad)
local USER_CFRAME_TO_THUMBSTICK = {
    [Enum.UserCFrame.LeftHand] = Enum.KeyCode.Thumbstick1,
    [Enum.UserCFrame.RightHand] = Enum.KeyCode.Thumbstick2,
}

local Workspace = game:GetService("Workspace")
local Players = game:GetService("Players")
local HttpService = game:GetService("HttpService")
local ContextActionService = game:GetService("ContextActionService")
local TweenService = game:GetService("TweenService")
local VRService = game:GetService("VRService")

local NexusVRCharacterModel = script.Parent.Parent.Parent
local NexusVRCharacterModelApi = require(NexusVRCharacterModel).Api
local Character = require(NexusVRCharacterModel:WaitForChild("Character"))
local CameraService = require(NexusVRCharacterModel:WaitForChild("State"):WaitForChild("CameraService")).GetInstance()
local CharacterService = require(NexusVRCharacterModel:WaitForChild("State"):WaitForChild("CharacterService")).GetInstance()
local Settings = require(NexusVRCharacterModel:WaitForChild("State"):WaitForChild("Settings")).GetInstance()
local VRInputService = require(NexusVRCharacterModel:WaitForChild("State"):WaitForChild("VRInputService")).GetInstance()

local BaseController = {}
BaseController.__index = BaseController

export type BaseController = {
    Active: boolean,
    ActionsToLock: {Enum.KeyCode},
    ActionsToUnbind: {Enum.KeyCode}?,
    Character: Character.Character?,
    Connections: {RBXScriptConnection}?,
    LastHeadCFrame: CFrame?,
    LastRotationUpdateTick: number?,
} & typeof(setmetatable({}, BaseController))

--[[
Returns the Y-axis angle of the given CFrame.
--]]
local function GetAngleToGlobalY(CF: CFrame): number
    return math.atan2(-CF.LookVector.X, -CF.LookVector.Z)
end

--[[
Creates a base controller object.
--]]
function BaseController.new(): BaseController
    return setmetatable({
        Active = false,
        ActionsToLock = {Enum.KeyCode.ButtonR3},
    }, BaseController) :: BaseController
end

--[[
Updates the character. Returns if it changed.
--]]
function BaseController.UpdateCharacterReference(self: BaseController): boolean
    local LastCharacter = self.Character
    self.Character = CharacterService:GetCharacter(Players.LocalPlayer)
    if not self.Character then
        return false
    end
    return LastCharacter ~= self.Character
end

--[[
Enables the controller.
--]]
function BaseController.Enable(self: BaseController): ()
    if not self.Connections then self.Connections = {} end
    self.Active = true

    --Bind the actions to sink inputs from the PlayerScripts.
    if not self.ActionsToUnbind then self.ActionsToUnbind = {} end
    for _, KeyCode in self.ActionsToLock do
        local ActionName = HttpService:GenerateGUID()
        ContextActionService:BindActionAtPriority(ActionName, function()
            return self.Active and Enum.ContextActionResult.Sink or Enum.ContextActionResult.Pass
        end, false, Enum.ContextActionPriority.High.Value, KeyCode)
        table.insert(self.ActionsToUnbind :: {Enum.KeyCode}, ActionName)
    end

    --Update the character and return if the character is nil.
    self:UpdateCharacterReference()
    if not self.Character then
        return
    end

    --Connect the eye level being set.
    local Connections = self.Connections :: {RBXScriptConnection}
    table.insert(Connections, VRInputService.EyeLevelSet:Connect(function()
        local LastHeadCFrame = self.LastHeadCFrame
        if LastHeadCFrame and LastHeadCFrame.Y > 0 then
            self.LastHeadCFrame = CFrame.new(0, -LastHeadCFrame.Y, 0) * LastHeadCFrame
        end
    end) :: any)

    --Connect the character entering a seat.
    table.insert(Connections, self.Character.Humanoid:GetPropertyChangedSignal("SeatPart"):Connect(function()
        local SeatPart = self.Character:GetHumanoidSeatPart()
        if SeatPart then
            self:PlayBlur()
            VRInputService:Recenter()
        end
    end))

    --Disable auto rotate so that the default controls work.
    self.Character.Humanoid.AutoRotate = false
end

--[[
Disables the controller.
--]]
function BaseController.Disable(self: BaseController): ()
    self.Active = false
    self.Character = nil
    self.LastHeadCFrame = nil
    self.LastRotationUpdateTick = nil
    if self.Connections then
        for _, Connection in self.Connections do
            Connection:Disconnect()
        end
    end
    if self.ActionsToUnbind then
        for _, ActionName in self.ActionsToUnbind do
            ContextActionService:UnbindAction(ActionName)
        end
    end
    self.Connections = nil
end

--[[
Scales the local-space input CFrame based on
the height multiplier of the character.
--]]
function BaseController.ScaleInput(self: BaseController, InputCFrame: CFrame): CFrame
    --Return the original CFrame if there is no character.
    if not self.Character or not InputCFrame then
        return InputCFrame
    end

    --Return the modified CFrame.
    return CFrame.new(InputCFrame.Position * (self.Character:GetHumanoidScale("BodyHeightScale") - 1)) * InputCFrame
end

--[[
Updates the provided 'Store' table with the state of its
Thumbstick (Enum.KeyCode.ThumbstickX) field. Returns the
direction state, radius state, and overall state change.
--]]
function BaseController.GetJoystickState(self: BaseController, Store: any): (string, string, string)
    local InputPosition = VRInputService:GetThumbstickPosition(Store.Thumbstick)
    local InputRadius = ((InputPosition.X ^ 2) + (InputPosition.Y ^ 2)) ^ 0.5
    local InputAngle = math.atan2(InputPosition.X, InputPosition.Y)

    local DirectionState, RadiusState
    if InputAngle >= math.rad(-135) and InputAngle <= math.rad(-45) then
        DirectionState = "Left"
    elseif InputAngle >= math.rad(-45) and InputAngle <= math.rad(45) then
        DirectionState = "Forward"
    elseif InputAngle >= math.rad(45) and InputAngle <= math.rad(135) then
        DirectionState = "Right"
    end
    if InputRadius >= THUMBSTICK_INPUT_START_RADIUS then
        RadiusState = "Extended"
    elseif InputRadius <= THUMBSTICK_INPUT_RELEASE_RADIUS then
        RadiusState = "Released"
    else
        RadiusState = "InBetween"
    end

    --Update the stored state.
    local StateChange = nil
    if RadiusState == "Released" then
        if Store.RadiusState == "Extended" then
            StateChange = "Released"
        end
        Store.RadiusState = "Released"
        Store.DirectionState = nil
    elseif RadiusState == "Extended" then
        if Store.RadiusState == nil or Store.RadiusState == "Released" then
            if Store.RadiusState ~= "Extended" then
                StateChange = "Extended"
            end
            Store.RadiusState = "Extended"
            Store.DirectionState = DirectionState
        elseif Store.DirectionState ~= DirectionState then
            if Store.RadiusState ~= "Cancelled" then
                StateChange = "Cancel"
            end
            Store.RadiusState = "Cancelled"
            Store.DirectionState = nil
        end
    end

    return DirectionState, RadiusState, StateChange
end

--[[
Plays a temporary blur effect to make
teleports and snap turns less jarring.
]]--
function BaseController.PlayBlur(self: BaseController): ()
    local SnapTeleportBlur = Settings:GetSetting("Movement.SnapTeleportBlur")
    SnapTeleportBlur = (if SnapTeleportBlur == nil then true else SnapTeleportBlur)

    if not SnapTeleportBlur then
        return
    end

    local Blur = Instance.new("BlurEffect")
    Blur.Parent = workspace.CurrentCamera
    Blur.Size = 56

    local BlurTween = TweenService:Create(Blur, BLUR_TWEEN_INFO, { Size = 0 })
    BlurTween:Play()

    BlurTween.Completed:Connect(function()
        Blur:Destroy()
    end)
end

--[[
Updates the reference world CFrame.
--]]
function BaseController.UpdateCharacter(self: BaseController): ()
    --Return if the character is nil.
    local CharacterChanged = self:UpdateCharacterReference()
    if not self.Character then
        return
    end
    if CharacterChanged then
        self:Enable()
    end

    --Get the VR inputs.
    local VRInputs = VRInputService:GetVRInputs()
    local VRHeadCFrame = self:ScaleInput(VRInputs[Enum.UserCFrame.Head])
    local VRLeftHandCFrame,VRRightHandCFrame = self:ScaleInput(VRInputs[Enum.UserCFrame.LeftHand]), self:ScaleInput(VRInputs[Enum.UserCFrame.RightHand])
    local HeadToLeftHandCFrame = VRHeadCFrame:Inverse() * VRLeftHandCFrame
    local HeadToRightHandCFrame = VRHeadCFrame:Inverse() * VRRightHandCFrame

    --Update the character.
    local SeatPart = self.Character:GetHumanoidSeatPart()
    if not SeatPart then
        --Offset the character by the change in the head input.
        if self.LastHeadCFrame then
            --Get the eye CFrame of the current character, except the Y offset from the HumanoidRootPart.
            --The Y position will be added absolutely since doing it relatively will result in floating or short characters.
            local HumanoidRootPartCFrame = self.Character.Parts.HumanoidRootPart.CFrame
            local LowerTorsoCFrame = HumanoidRootPartCFrame * self.Character.Attachments.HumanoidRootPart.RootRigAttachment.CFrame * CFrame.new(0, -self.Character.Motors.Root.Transform.Position.Y, 0) * self.Character.Motors.Root.Transform * self.Character.Attachments.LowerTorso.RootRigAttachment.CFrame:Inverse()
            local UpperTorsoCFrame = LowerTorsoCFrame * self.Character.Attachments.LowerTorso.WaistRigAttachment.CFrame * self.Character.Motors.Waist.Transform * self.Character.Attachments.UpperTorso.WaistRigAttachment.CFrame:Inverse()
            local HeadCFrame = UpperTorsoCFrame * self.Character.Attachments.UpperTorso.NeckRigAttachment.CFrame * self.Character.Motors.Neck.Transform * self.Character.Attachments.Head.NeckRigAttachment.CFrame:Inverse()
            local EyesOffset = self.Character.Head:GetEyesOffset()
            local CharacterEyeCFrame = HeadCFrame * EyesOffset

            --Determine the input components.
            local InputDelta = self.LastHeadCFrame:Inverse() * VRHeadCFrame
            if VRHeadCFrame.UpVector.Y < 0 then
                InputDelta = CFrame.Angles(0,math.pi,0) * InputDelta
            end
            local HeadRotationXZ = (CFrame.new(VRHeadCFrame.Position) * CFrame.Angles(0, math.atan2(-VRHeadCFrame.LookVector.X, -VRHeadCFrame.LookVector.Z), 0)):Inverse() * VRHeadCFrame
            local LastHeadAngleY = GetAngleToGlobalY(self.LastHeadCFrame)
            local HeadAngleY = GetAngleToGlobalY(VRHeadCFrame)
            local HeightOffset = CFrame.new(0, (CFrame.new(0, EyesOffset.Y, 0) * (VRHeadCFrame * EyesOffset:Inverse())).Y, 0)

            --Offset the character eyes for the current input.
            local CurrentCharacterAngleY = GetAngleToGlobalY(CharacterEyeCFrame)
            local RotationY = CFrame.Angles(0, CurrentCharacterAngleY + (HeadAngleY - LastHeadAngleY), 0)
            local NewCharacterEyePosition = (HeightOffset *  CFrame.new((RotationY * CFrame.new(InputDelta.X, 0, InputDelta.Z)).Position) * CharacterEyeCFrame).Position
            local NewCharacterEyeCFrame = CFrame.new(NewCharacterEyePosition) * RotationY * HeadRotationXZ

            --Update the character.
            self.Character:UpdateFromInputs(NewCharacterEyeCFrame, NewCharacterEyeCFrame * HeadToLeftHandCFrame,NewCharacterEyeCFrame * HeadToRightHandCFrame)
        end
    else
        --Set the absolute positions of the character.
        self.Character:UpdateFromInputsSeated(VRHeadCFrame, VRHeadCFrame * HeadToLeftHandCFrame,VRHeadCFrame * HeadToRightHandCFrame)
    end

    --Update the camera.
    if self.Character.Parts.HumanoidRootPart:IsDescendantOf(Workspace) and self.Character.Humanoid.Health > 0 then
        --Update the camera based on the character.
        --Done based on the HumanoidRootPart instead of the Head because of Motors not updating the same frame, leading to a delay.
        local HumanoidRootPartCFrame = self.Character.Parts.HumanoidRootPart.CFrame
        local LowerTorsoCFrame = HumanoidRootPartCFrame * self.Character.Attachments.HumanoidRootPart.RootRigAttachment.CFrame * self.Character.Motors.Root.Transform * self.Character.Attachments.LowerTorso.RootRigAttachment.CFrame:Inverse()
        local UpperTorsoCFrame = LowerTorsoCFrame * self.Character.Attachments.LowerTorso.WaistRigAttachment.CFrame * self.Character.Motors.Waist.Transform * self.Character.Attachments.UpperTorso.WaistRigAttachment.CFrame:Inverse()
        local HeadCFrame = UpperTorsoCFrame * self.Character.Attachments.UpperTorso.NeckRigAttachment.CFrame * self.Character.Motors.Neck.Transform * self.Character.Attachments.Head.NeckRigAttachment.CFrame:Inverse()
        CameraService:UpdateCamera(HeadCFrame * self.Character.Head:GetEyesOffset())
        self.LastHeadCFrame = VRHeadCFrame
    elseif not Workspace.CurrentCamera.HeadLocked then
        --Update the camera based on the last CFrame if the motors can't update (not in Workspace).
        local CurrentCameraCFrame = Workspace.CurrentCamera:GetRenderCFrame()
        local LastHeadCFrame = self.LastHeadCFrame or CFrame.new()
        local HeadCFrame = self:ScaleInput(VRInputService:GetVRInputs()[Enum.UserCFrame.Head])
        CameraService:UpdateCamera(CurrentCameraCFrame * LastHeadCFrame:Inverse() * HeadCFrame)
        self.LastHeadCFrame = HeadCFrame
    end
end

--[[
Performs snap or smooth rotating based on the thumbstick input.
--]]
function BaseController.UpdateRotating(self: BaseController, Hand: Enum.UserCFrame, Direction: string, StateChange: string): ()
    if VRService.AvatarGestures then
        self.LastRotationUpdateTick = nil
        return
    end
    if not self.Character or self.Character.Humanoid.Sit then
        self.LastRotationUpdateTick = nil
        return
    end
    if Direction ~= "Left" and Direction ~= "Right" then
        self.LastRotationUpdateTick = nil
        return
    end

    --Return if the input is inactive.
    if NexusVRCharacterModelApi.Controller and not NexusVRCharacterModelApi.Controller:IsControllerInputEnabled(Hand) then
        return
    end

    --Rotate the character.
    local HumanoidRootPart = self.Character.Parts.HumanoidRootPart
    if UserSettings():GetService("UserGameSettings").VRSmoothRotationEnabled then
        --Smoothly rotate the character.
        local InputPosition = VRInputService:GetThumbstickPosition(USER_CFRAME_TO_THUMBSTICK[Hand])
        if math.abs(InputPosition.X) >= THUMBSTICK_SMOOTH_LOCOMOTION_DEADZONE then
            local LastRotationUpdateTick = self.LastRotationUpdateTick or tick()
            local CurrentRotationUpdateTick = tick()
            local RotationUpdateDeltaTime = (CurrentRotationUpdateTick - LastRotationUpdateTick)
            HumanoidRootPart.CFrame = CFrame.new(HumanoidRootPart.Position) * CFrame.Angles(0, -InputPosition.X * THUMBSTICK_MANUAL_SMOOTH_ROTATION_RATE * RotationUpdateDeltaTime, 0) * (CFrame.new(-HumanoidRootPart.Position) * HumanoidRootPart.CFrame)
            self.LastRotationUpdateTick = CurrentRotationUpdateTick
        else
            self.LastRotationUpdateTick = nil
        end
    else
        --Snap rotate the character.
        if StateChange == "Extended" then
            if Direction == "Left" then
                --Turn the player to the left.
                self:PlayBlur()
                HumanoidRootPart.CFrame = CFrame.new(HumanoidRootPart.Position) * CFrame.Angles(0, THUMBSTICK_SNAP_ROTATION_ANGLE, 0) * (CFrame.new(-HumanoidRootPart.Position) * HumanoidRootPart.CFrame)
            elseif Direction == "Right" then
                --Turn the player to the right.
                self:PlayBlur()
                HumanoidRootPart.CFrame = CFrame.new(HumanoidRootPart.Position) * CFrame.Angles(0, -THUMBSTICK_SNAP_ROTATION_ANGLE, 0) * (CFrame.new(-HumanoidRootPart.Position) * HumanoidRootPart.CFrame)
            end
        end
    end
end

return BaseController


--Local character controller using teleporting.
--!strict

local BaseController = require(script.Parent:WaitForChild("BaseController"))

local SmoothLocomotionController = {}
SmoothLocomotionController.__index = SmoothLocomotionController
setmetatable(SmoothLocomotionController, BaseController)

export type SmoothLocomotionController = {
    JoystickState: {[string]: Enum.KeyCode}?,
} & typeof(setmetatable({}, SmoothLocomotionController)) & BaseController.BaseController

--[[
Creates a smooth locomotion controller object.
--]]
function SmoothLocomotionController.new(): SmoothLocomotionController
    return setmetatable(BaseController.new(), SmoothLocomotionController) :: SmoothLocomotionController
end

--[[
Enables the controller.
--]]
function SmoothLocomotionController.Enable(self: SmoothLocomotionController): ()
    BaseController.Enable(self)
    self.JoystickState = {Thumbstick = Enum.KeyCode.Thumbstick2}
end

--[[
Disables the controller.
--]]
function SmoothLocomotionController.Disable(self: SmoothLocomotionController): ()
    BaseController.Disable(self)
    self.JoystickState = nil
end

--[[
Updates the local character. Must also update the camara.
--]]
function SmoothLocomotionController.UpdateCharacter(self: SmoothLocomotionController): ()
    --Update the base character.
    BaseController.UpdateCharacter(self)
    if not self.Character then
        return
    end

    --Rotate the character.
    local DirectionState, _, StateChange = self:GetJoystickState(self.JoystickState)
    self:UpdateRotating(Enum.UserCFrame.RightHand, DirectionState, StateChange)
end

return SmoothLocomotionController


 --Local character controller using teleporting.
--!strict

local Workspace = game:GetService("Workspace")

local NexusVRCharacterModel = script.Parent.Parent.Parent
local NexusVRCharacterModelApi = require(NexusVRCharacterModel).Api
local BaseController = require(script.Parent:WaitForChild("BaseController"))
local ArcWithBeacon = require(script.Parent:WaitForChild("Visual"):WaitForChild("ArcWithBeacon"))
local VRInputService = require(NexusVRCharacterModel:WaitForChild("State"):WaitForChild("VRInputService")).GetInstance()

local TeleportController = {}
TeleportController.__index = TeleportController
setmetatable(TeleportController, BaseController)

export type TeleportController = {
    LeftArc: ArcWithBeacon.ArcWithBeacon,
    RightArc: ArcWithBeacon.ArcWithBeacon,
    ArcControls: {
        {
            Thumbstick: Enum.KeyCode,
            UserCFrame: Enum.UserCFrame,
            Arc: ArcWithBeacon.ArcWithBeacon,
            WaitForRelease: boolean?,
            RadiusState: ("Forward" | "Released")?,
            LastHitPart: BasePart?,
            LastHitPosition: Vector3?,
        }
    },
    IgnoreNextExternalTeleport: boolean?,
} & typeof(setmetatable({}, TeleportController)) & BaseController.BaseController

--[[
Creates a teleport controller object.
--]]
function TeleportController.new(): TeleportController
    local self = setmetatable(BaseController.new(), TeleportController) :: TeleportController
    self.ActionsToLock = {Enum.KeyCode.Thumbstick1, Enum.KeyCode.ButtonR3}
    return self
end

--[[
Enables the controller.
--]]
function TeleportController.Enable(self: TeleportController): ()
    BaseController.Enable(self)

    --Create the arcs.
    self.LeftArc = ArcWithBeacon.new()
    self.RightArc = ArcWithBeacon.new()
    self.ArcControls = {
        {
            Thumbstick = Enum.KeyCode.Thumbstick1,
            UserCFrame = Enum.UserCFrame.LeftHand,
            Arc = self.LeftArc,
        },
        {
            Thumbstick = Enum.KeyCode.Thumbstick2,
            UserCFrame = Enum.UserCFrame.RightHand,
            Arc = self.RightArc,
        },
    }
end

--[[
Disables the controller.
--]]
function TeleportController.Disable(self: TeleportController): ()
    BaseController.Disable(self)

    --Destroy the arcs.
    self.LeftArc:Destroy()
    self.RightArc:Destroy()
end

--[[
Updates the local character. Must also update the camara.
--]]
function TeleportController.UpdateCharacter(self: TeleportController): ()
    --Update the base character.
    BaseController.UpdateCharacter(self)
    if not self.Character then
        return
    end

    --Get the VR inputs.
    local VRInputs = VRInputService:GetVRInputs()
    for _, InputEnum in Enum.UserCFrame:GetEnumItems() do
        VRInputs[InputEnum] = self:ScaleInput(VRInputs[InputEnum])
    end

    --Update the arcs.
    local SeatPart = self.Character:GetHumanoidSeatPart()
    for _, ArcData in self.ArcControls do
        --Reset the left arc if the player is in a vehicle seat.
        if ArcData.Thumbstick == Enum.KeyCode.Thumbstick1 and SeatPart and SeatPart:IsA("VehicleSeat") then
            ArcData.Arc:Hide()
            continue
        end

        --Update and fetch the current state.
        local InputActive = (not NexusVRCharacterModelApi.Controller or NexusVRCharacterModelApi.Controller:IsControllerInputEnabled(ArcData.UserCFrame))
        local DirectionState, RadiusState, StateChange = self:GetJoystickState(ArcData)
        if not InputActive then
            ArcData.Arc:Hide()
            ArcData.WaitForRelease = false
            ArcData.RadiusState = nil
            continue
        end

        --Update from the state.
        local HumanoidRootPart = self.Character.Parts.HumanoidRootPart
        if DirectionState ~= "Forward" or RadiusState == "Released" then
            ArcData.Arc:Hide()
        end
        if StateChange == "Released" then
            ArcData.Arc:Hide()
            if DirectionState == "Forward" then
                --Teleport the player.
                local LastHitPart = ArcData.LastHitPart
                if LastHitPart and ArcData.LastHitPosition then
                    --Unsit the player.
                    --The teleport event is set to ignored since the CFrame will be different when the player gets out of the seat.
                    local WasSitting = false
                    self:PlayBlur()

                    if SeatPart then
                        WasSitting = true
                        self.IgnoreNextExternalTeleport = true
                        self.Character.Humanoid.Sit = false
                    end

                    if (LastHitPart:IsA("Seat") or LastHitPart:IsA("VehicleSeat")) and not LastHitPart.Occupant and not LastHitPart.Disabled then
                        --Sit in the seat.
                        --Waiting is done if the player was in an existing seat because the player no longer sitting will prevent sitting.
                        local LastHitSeat = (LastHitPart :: Seat)
                        if WasSitting then
                            task.spawn(function()
                                while self.Character.Humanoid.SeatPart do task.wait() end
                                LastHitSeat:Sit(self.Character.Humanoid)
                            end)
                        else
                            LastHitSeat:Sit(self.Character.Humanoid)
                        end
                    else
                        --Teleport the player.
                        --Waiting is done if the player was in an existing seat because the player will teleport the seat.
                        if WasSitting then
                            task.spawn(function()
                                while self.Character.Humanoid.SeatPart do task.wait() end
                                HumanoidRootPart.CFrame = CFrame.new(ArcData.LastHitPosition) * CFrame.new(0, 4.5 * self.Character:GetHumanoidScale("BodyHeightScale"), 0) * (CFrame.new(-HumanoidRootPart.Position) * HumanoidRootPart.CFrame)
                            end)
                        else
                            HumanoidRootPart.CFrame = CFrame.new(ArcData.LastHitPosition) * CFrame.new(0, 4.5 * self.Character:GetHumanoidScale("BodyHeightScale"), 0) * (CFrame.new(-HumanoidRootPart.Position) * HumanoidRootPart.CFrame)
                        end
                    end
                end
            end
        elseif StateChange == "Cancel" then
            ArcData.Arc:Hide()
        elseif DirectionState == "Forward" and RadiusState == "Extended" then
            ArcData.LastHitPart, ArcData.LastHitPosition = ArcData.Arc:Update(Workspace.CurrentCamera:GetRenderCFrame() * VRInputs[Enum.UserCFrame.Head]:Inverse() * VRInputs[ArcData.UserCFrame])
        end

        --Rotate the character.
        self:UpdateRotating(ArcData.UserCFrame, DirectionState, StateChange)
    end
end

return TeleportController


--Visual indicator aiming with an arc.
--!strict

local MAX_SEGMENTS = 100
local SEGMENT_SEPARATION = 2
local BASE_POINTER_ANGLE = math.rad(60)
local POINTER_PARABOLA_HEIGHT_MULTIPLIER = -0.2

local Workspace = game:GetService("Workspace")
local Players = game:GetService("Players")

local NexusVRCharacterModel = script.Parent.Parent.Parent.Parent
local FindCollidablePartOnRay = require(NexusVRCharacterModel:WaitForChild("Util"):WaitForChild("FindCollidablePartOnRay"))

local Arc = {}
Arc.__index = Arc

export type Arc = {
    BeamParts: {BasePart},
} & typeof(setmetatable({}, Arc))

--[[
Creates an arc.
--]]
function Arc.new(): Arc
    local self = setmetatable({
        BeamParts = {},
    }, Arc) :: Arc
    self:Hide()
    return self
end

--[[
Updates the arc. Returns the part and
position that were hit.
--]]
function Arc.Update(self: Arc, StartCFrame: CFrame): (BasePart?, Vector3?)
    --Calculate the starting angle.
    local StartPosition = StartCFrame.Position
    local FaceAngle = math.atan2(-StartCFrame.LookVector.X, -StartCFrame.LookVector.Z)
    local StartAngle = math.asin(StartCFrame.LookVector.Y)
    StartAngle = StartAngle + (BASE_POINTER_ANGLE * ((math.pi / 2) - math.abs(StartAngle)) / (math.pi / 2))

    --Calculate the start CFrame and start offset of the parabola.
    --The start is the where the derivative of POINTER_PARABOLA_HEIGHT_MULTIPLIER * x^2 is tan(StartAngle).
    local StartCF = CFrame.new(StartPosition) * CFrame.Angles(0, FaceAngle, 0)
    local StartOffset = math.tan(StartAngle) / (POINTER_PARABOLA_HEIGHT_MULTIPLIER * 2)
    local StartValue = POINTER_PARABOLA_HEIGHT_MULTIPLIER * (StartOffset ^ 2)

    --Create the parts until the limit is reached.
    for i = 0, MAX_SEGMENTS - 1 do
        --Calculate the current and next position.
        local SegmentStartPosition = (StartCF * CFrame.new(0, POINTER_PARABOLA_HEIGHT_MULTIPLIER * ((i + StartOffset) ^ 2) - StartValue, -SEGMENT_SEPARATION * i)).Position
        local SegmentEndPosition = (StartCF * CFrame.new(0, POINTER_PARABOLA_HEIGHT_MULTIPLIER * ((i + 1 + StartOffset) ^ 2) - StartValue, -SEGMENT_SEPARATION * (i + 1))).Position

        --Create the parts if they don't exist.
        if not self.BeamParts[i] then
            self.BeamParts[i] = Instance.new("Part")
            self.BeamParts[i].Transparency = 1
            self.BeamParts[i].Size = Vector3.new(0, 0, 0)
            self.BeamParts[i].Anchored = true
            self.BeamParts[i].CanCollide = false
            self.BeamParts[i].CanQuery = false
            self.BeamParts[i].Parent = Workspace.CurrentCamera

            local Attachment = Instance.new("Attachment")
            Attachment.Name = "BeamAttachment"
            Attachment.CFrame = CFrame.Angles(0, 0, math.pi / 2)
            Attachment.Parent = self.BeamParts[i]
        end
        if not self.BeamParts[i + 1] then
            --Create the part and attachment.
            self.BeamParts[i + 1] = Instance.new("Part")
            self.BeamParts[i + 1].Transparency = 1
            self.BeamParts[i + 1].Size = Vector3.new(0, 0, 0)
            self.BeamParts[i + 1].Anchored = true
            self.BeamParts[i + 1].CanCollide = false
            self.BeamParts[i + 1].CanQuery = false
            self.BeamParts[i + 1].Parent = Workspace.CurrentCamera

            local Attachment = Instance.new("Attachment")
            Attachment.Name = "BeamAttachment"
            Attachment.CFrame = CFrame.Angles(0, 0, math.pi / 2)
            Attachment.Parent = self.BeamParts[i + 1]

            --Create the beam.
            local Beam = Instance.new("Beam")
            Beam.Name = "Beam"
            Beam.Attachment0 = (self.BeamParts[i] :: any).BeamAttachment
            Beam.Attachment1 = Attachment
            Beam.Segments = 1
            Beam.Width0 = 0.1
            Beam.Width1 = 0.1
            Beam.Parent = self.BeamParts[i + 1]
        end

        --Cast the ray to the end.
        --Return if an end was hit and make the arc blue.
        local HitPart, HitPosition = FindCollidablePartOnRay(SegmentStartPosition, SegmentEndPosition - SegmentStartPosition, Players.LocalPlayer and Players.LocalPlayer.Character,Players.LocalPlayer and Players.LocalPlayer.Character and Players.LocalPlayer.Character:FindFirstChild("HumanoidRootPart"))
        self.BeamParts[i].CFrame = CFrame.new(SegmentStartPosition) * CFrame.Angles(0, FaceAngle, 0);
        (self.BeamParts[i + 1] :: any).Beam.Enabled = true
        if HitPart then
            self.BeamParts[i + 1].CFrame = CFrame.new(HitPosition)
            for j = 0, i do
                (self.BeamParts[j + 1] :: any).Beam.Color = ColorSequence.new(Color3.fromRGB(0, 170, 255))
            end
            for j = i + 1, #self.BeamParts - 1 do
                (self.BeamParts[j + 1] :: any).Beam.Enabled = false
            end
            return HitPart, HitPosition
        else
            self.BeamParts[i + 1].CFrame = CFrame.new(SegmentEndPosition)
        end
    end

    --Set the beams to red.
    for i = 0, #self.BeamParts - 1 do
        (self.BeamParts[i + 1] :: any).Beam.Color = ColorSequence.new(Color3.fromRGB(200, 0, 0))
    end
    return nil, nil
end

--[[
Hides the arc.
--]]
function Arc.Hide(self: Arc): ()
    for i = 0, #self.BeamParts - 1 do
        (self.BeamParts[i + 1] :: any).Beam.Enabled = false
    end
end

--[[
Destroys the arc.
--]]
function Arc.Destroy(self: Arc): ()
    for _, BeamPart in self.BeamParts do
        BeamPart:Destroy()
    end
    self.BeamParts = {}
end

return Arc


--Extension of the arc to add a beacon.
--!strict

local Arc = require(script.Parent:WaitForChild("Arc"))
local Beacon = require(script.Parent:WaitForChild("Beacon"))

local ArcWithBeacon = {}
ArcWithBeacon.__index = ArcWithBeacon
setmetatable(ArcWithBeacon, Arc)

export type ArcWithBeacon = {
    Beacon: Beacon.Beacon,
} & typeof(setmetatable({}, ArcWithBeacon)) & Arc.Arc

--[[
Creates an arc.
--]]
function ArcWithBeacon.new(): ArcWithBeacon
    local self = setmetatable(Arc.new() :: any, ArcWithBeacon) :: ArcWithBeacon
    self.Beacon = Beacon.new()
    self:Hide()
    return self :: ArcWithBeacon
end

--[[
Updates the arc. Returns the part and
position that were hit.
--]]
function ArcWithBeacon.Update(self: ArcWithBeacon, StartCFrame: CFrame): (BasePart?, Vector3?)
    --Update the arc.
    local HitPart, HitPosition = Arc.Update(self, StartCFrame)

    --Update the beacon.
    local Beacon = (self :: any).Beacon :: Beacon.Beacon
    if HitPart then
        Beacon:Update(CFrame.new(HitPosition :: Vector3) * CFrame.new(0, 0.001, 0), HitPart)
    else
        Beacon:Hide()
    end

    --Return the arc's returns.
    return HitPart, HitPosition
end

--[[
Hides the arc.
--]]
function ArcWithBeacon.Hide(self: ArcWithBeacon): ()
    Arc.Hide(self);
    self.Beacon:Hide()
end

--[[
Destroys the arc.
--]]
function ArcWithBeacon.Destroy(self: ArcWithBeacon): ()
    Arc.Destroy(self);
    self.Beacon:Destroy()
end

return ArcWithBeacon


--Visual indicator for the end of aiming.
--!strict

local BEACON_SPEED_MULTIPLIER = 2

local Workspace = game:GetService("Workspace")

local Beacon = {}
Beacon.__index = Beacon

export type Beacon = {
    Sphere: Part,
    ConstantRing: ImageHandleAdornment,
    MovingRing: ImageHandleAdornment,
} & typeof(setmetatable({}, Beacon))

--[[
Creates a beacon.
--]]
function Beacon.new(): Beacon
    --Create the object.
    local self = {}
    setmetatable(self, Beacon)

    --Create the parts.
    local Sphere = Instance.new("Part")
    Sphere.Transparency = 1
    Sphere.Material = Enum.Material.Neon
    Sphere.Anchored = true
    Sphere.CanCollide = false
    Sphere.CanQuery = false
    Sphere.Size = Vector3.new(0.5, 0.5, 0.5)
    Sphere.Shape = Enum.PartType.Ball
    Sphere.Parent = Workspace.CurrentCamera

    local ConstantRing = Instance.new("ImageHandleAdornment")
    ConstantRing.Adornee = Sphere
    ConstantRing.Size = Vector2.new(2, 2)
    ConstantRing.Image = "rbxasset://textures/ui/VR/VRPointerDiscBlue.png"
    ConstantRing.Visible = false
    ConstantRing.Parent = Sphere

    local MovingRing = Instance.new("ImageHandleAdornment")
    MovingRing.Adornee = Sphere
    MovingRing.Size = Vector2.new(2, 2)
    MovingRing.Image = "rbxasset://textures/ui/VR/VRPointerDiscBlue.png"
    MovingRing.Visible = false
    MovingRing.Parent = Sphere

    --Return the object.
    return setmetatable({
        Sphere = Sphere,
        ConstantRing = ConstantRing,
        MovingRing = MovingRing,
    }, Beacon) :: Beacon
end

--[[
Updates the beacon at a given CFrame.
--]]
function Beacon.Update(self: Beacon, CenterCFrame: CFrame, HoverPart: BasePart): ()
    --Calculate the size for the current time.
    local Height = 0.4 + (-math.cos(tick() * 2 * BEACON_SPEED_MULTIPLIER) / 8)
    local BeaconSize = 2 * ((tick() * BEACON_SPEED_MULTIPLIER) % math.pi) / math.pi

    --Update the size and position of the beacon.
    self.Sphere.CFrame = CenterCFrame * CFrame.new(0, Height, 0)
    self.ConstantRing.CFrame = CFrame.new(0, -Height, 0) * CFrame.Angles(math.pi / 2, 0, 0)
    self.MovingRing.CFrame = CFrame.new(0, -Height, 0) * CFrame.Angles(math.pi / 2, 0, 0)
    self.MovingRing.Transparency = BeaconSize / 2
    self.MovingRing.Size = Vector2.new(BeaconSize, BeaconSize)

    --Update the beacon color.
    local BeaconColor = Color3.fromRGB(0, 170, 0)
    if HoverPart then
        local VRBeaconColor = HoverPart:FindFirstChild("VRBeaconColor") :: Color3Value
        if VRBeaconColor then
            BeaconColor = VRBeaconColor.Value
        elseif (HoverPart:IsA("Seat") or HoverPart:IsA("VehicleSeat")) and not HoverPart.Disabled then
            BeaconColor = Color3.fromRGB(0, 170, 255)
        end
    end
    self.Sphere.Color = BeaconColor

    --Show the beacon.
    self.Sphere.Transparency = 0
    self.ConstantRing.Visible = true
    self.MovingRing.Visible = true
end

--[[
Hides the beacon.
--]]
function Beacon.Hide(self: Beacon): ()
    --Hide the beacon.
    self.Sphere.Transparency = 1
    self.ConstantRing.Visible = false
    self.MovingRing.Visible = false
end

--[[
Destroys the beacon.
--]]
function Beacon.Destroy(self: Beacon): ()
    self.Sphere:Destroy()
end

return Beacon