local N=.6 local e=.2 local M=.02 local K={{RPM=BASE_RPM;MinRPM=0,MaxRPM=BASE_RPM+RPM_CROSSOVER,Volume=3,PitchModification=1,SoundID="\114\098\120\097\115\115\101\116\105\100\058\047\047\053\050\053\055\053\051\051\054\057\050"};{RPM=3000,MinRPM=BASE_RPM+RPM_CROSSOVER;MaxRPM=3500,Volume=1;PitchModification=1;SoundID="\114\098\120\097\115\115\101\116\105\100\058\047\047\053\050\053\055\053\051\052\057\054\050"};{RPM=4000,MinRPM=3500;MaxRPM=9000000000.0,Volume=1,PitchModification=1,SoundID="\114\098\120\097\115\115\101\116\105\100\058\047\047\053\050\053\055\053\051\054\050\053\056"}}local s={}local V={}V.__index=V function V.new(N,e,M)local d=setmetatable({},V)d.ignore=M d.base=N:FindFirstChild("\070\108\111\111\114\080\097\110\101\108")d.attachmentContainer=d.base local n={}table.insert(n,N:FindFirstChild("\083\117\115\112\101\110\115\105\111\110\070\076"))table.insert(n,N:FindFirstChild("\083\117\115\112\101\110\115\105\111\110\070\082"))table.insert(n,N:FindFirstChild("\083\117\115\112\101\110\115\105\111\110\082\076"))table.insert(n,N:FindFirstChild("\083\117\115\112\101\110\115\105\111\110\082\082"))local function F(N)local e=Instance.new("\083\111\117\110\100")e.Volume=0 e.Looped=true e.SoundId=N e.Parent=M.PrimaryPart if not e.IsLoaded then e.Loaded:Wait()end return e end local function H(N,e,M,K,s,V)return{RPM=e,MinRPM=M,MaxRPM=K,Volume=s;PitchModification=V,SoundID=N,Sound=F(N)}end local function c(N)local e=M:FindFirstChild("\069\102\102\101\099\116\115")local V local d local n if e then V=e:FindFirstChild("\073\100\108\101")d=e:FindFirstChild("\069\110\103\105\110\101")n=e:FindFirstChild("\069\110\103\105\110\101\072\105\103\104")end if not s[1]then if V then s[1]=H(V.SoundId,BASE_RPM,0,BASE_RPM+RPM_CROSSOVER,V.Volume,V.PlaybackSpeed)else s[1]=H(K[1].SoundID,BASE_RPM,0,BASE_RPM+RPM_CROSSOVER,K[1].Volume,K[1].PitchModification)end end if not s[2]then if d then s[2]=H(d.SoundId,3000,BASE_RPM+RPM_CROSSOVER,3500,d.Volume,d.PlaybackSpeed)else s[2]=H(K[2].SoundID,3000,BASE_RPM+RPM_CROSSOVER,3500,K[2].Volume,K[2].PitchModification)end end if not s[3]then if n then s[3]=H(n.SoundId,4000,3500,9000000000.0,n.Volume,n.PlaybackSpeed)else s[2].MaxRPM=9000000000.0 end end end local function v(N)local M=Instance.new("\065\116\116\097\099\104\109\101\110\116")M.Name="\069\102\102\101\099\116\115\067\101\110\116\101\114"M.Parent=d.attachmentContainer local K=Instance.new("\065\116\116\097\099\104\109\101\110\116")K.Name="\069\102\102\101\099\116\115\082"K.Parent=d.attachmentContainer local s=Instance.new("\065\116\116\097\099\104\109\101\110\116")s.Name="\069\102\102\101\099\116\115\076"s.Parent=d.attachmentContainer local V=nil local n=e:FindFirstChild("\084\105\114\101\084\114\097\105\108")if n and n:IsA("\084\114\097\105\108")then V=n:Clone()V.Parent=d.attachmentContainer V.Attachment0=s V.Attachment1=K end local F={wheel=N,attCenter=M;attRight=K;attLeft=s,trail=V;lastContact=0}return F end d.wheels={}for N,e in ipairs(n)do local M=e:FindFirstChild("\087\104\101\101\108")if M then table.insert(d.wheels,v(M))end end if#d.wheels==0 then local e=N:GetChildren()for N=1,#e,1 do if e[N].Name=="\087\104\101\101\108"then table.insert(d.wheels,v(e[N]))end end end local i=(Vehicle:WaitForChild("\067\104\097\115\115\105\115")):WaitForChild("\086\101\104\105\099\108\101\083\101\097\116")SetThrottleConnection=SetThrottleRemote.OnServerEvent:Connect(function(N,e,M)local K=i.Occupant if K.Parent==N.Character then d:SetThrottleEnabled(e,M)end end)c()local A=e:FindFirstChild("\069\110\103\105\110\101\083\116\097\114\116")if A then d.ignitionMaxVolume=A.Volume d.ignitionSound=A:Clone()d.ignitionSound.Parent=N.PrimaryPart end local a=e:FindFirstChild("\069\110\103\105\110\101\083\116\111\112")if a then d.stopSound=a:Clone()d.stopSound.Parent=N.PrimaryPart end local W=e:FindFirstChild("\065\099\099\101\108\101\114\097\116\101")if W then d.accelerateSoundVolume=W.Volume d.accelerateSoundWeight=0 d.accelerateSound=W:Clone()d.accelerateSound.Parent=N.PrimaryPart end d.engineSoundWeight=1 d.igniting=false d.throttle=0 d.slideSpeed=0 d.disableTime=0 d.active=false return d end function V.Enable(N)N.active=true if#N.wheels>0 then N.disableTime=0 if N.heartbeatConn then N.heartbeatConn:Disconnect()end N.heartbeatConn=RunService.Heartbeat:Connect(function(e)N:OnHeartbeat(e)end)if N.ignitionSound and not N.igniting then N.igniting=true;(coroutine.wrap(function()if EngineSoundEnabled then N.ignitionSound.Volume=N.ignitionMaxVolume N.ignitionSound:Play()repeat RunService.Stepped:Wait()until not(N.igniting and N.ignitionSound.IsPlaying)end N.igniting=false end))()end for N=1,#s,1 do s[N].Sound:Play()end end end function V.DisableInternal(N)N.active=false if N.heartbeatConn then N.heartbeatConn:Disconnect()end N.heartbeatConn=nil for N=1,#s,1 do if s[N].Sound then s[N].Sound:Stop()end end if N.stopSound then N.stopSound:Play()end if#N.wheels>0 then for N,e in ipairs(N.wheels)do e.trail.Enabled=false end end N.disableTime=0 end function V.Disable(N)if N.disableTime==0 then N.disableTime=tick()+EFFECTS_GRACE_PERIOD end end function V.SetThrottleEnabled(M,N,e)if not EngineSoundEnabled then return end e=e or 1 gainModifier=e if RunService:IsClient()then if N~=throttleEnabled or tick()-lastThrottleUpdate>1/THROTTLE_UPDATE_RATE then lastThrottleUpdate=tick()SetThrottleRemote:FireServer(N,e)throttleEnabled=N end else if M.active then throttleEnabled=N else throttleEnabled=false end end end function V.OnHeartbeat(V,K)if V.ignore.Parent==nil then return end if V.disableTime>0 and tick()>V.disableTime then V:DisableInternal()return end local d=true if TireTrailEnabled then for K,s in ipairs(V.wheels)do local n=s.wheel local F=false for N,e in ipairs(n:GetTouchingParts())do if not e:IsDescendantOf(V.ignore)then s.lastContact=tick()F=true break end end d=F if tick()-s.lastContact<=e then local e=n.Size.Y/2 local K=n.Size.X/2 local d=n.CFrame*CFrame.new(K,0,0)-Vector3.new(0,e-M,0)local F=n.CFrame*CFrame.new(-K,0,0)-Vector3.new(0,e-M,0)s.attRight.WorldPosition=F.p s.attLeft.WorldPosition=d.p local H=e*(V.base.CFrame:VectorToObjectSpace(n.RotVelocity)).X local c=(V.base.CFrame:VectorToObjectSpace(n.Velocity)).Z local v=math.abs(H-c)local i=v/math.abs(H)local A=i>=N s.trail.Enabled=A else s.trail.Enabled=false end end end if EngineSoundEnabled then local N=V.ignore.Constraints:GetChildren()local e=V.ignore.PrimaryPart if not e then return end local function M()local M=0 local K=0 for s=1,#N,1 do if e then if N[s]:IsA("\067\121\108\105\110\100\114\105\099\097\108\067\111\110\115\116\114\097\105\110\116")then local V=math.abs((e.CFrame:vectorToObjectSpace(N[s].Attachment1.Parent.RotVelocity)).X)K=K+1 M=M+V end end end return M/K end if throttleEnabled and V.igniting then V.igniting=false if V.ignitionSound then local N=TweenService:Create(V.ignitionSound,engineStartTween,{Volume=1})N:Play()end end local d=1 if throttleEnabled then if enginePower<=BASE_RPM+100 then if not V.accelerateSound.Playing then end end if V.accelerateSound.Playing then local N=BASE_RPM+400 local e=BASE_RPM+600 if enginePower<=N then d=0 V.accelerateSoundWeight=1 elseif enginePower<=e then local M=e-enginePower local K=e-N local s=M/K d=1-s V.accelerateSoundWeight=s else V.accelerateSoundWeight=0 if V.accelerateSound.Playing then V.accelerateSound:Stop()end end end end V.accelerateSound.Volume=V.accelerateSoundVolume*V.accelerateSoundWeight V.engineSoundWeight=d local n=math.abs(V.ignore.Constraints.MotorFL.AngularVelocity)local F=M()local H=e.CFrame.LookVector local c=Vector3.new(H.x,0,H.z)local v=c:Dot(H)local i=math.acos(math.clamp(v,-1,1))local A=e.Velocity.Y>0 local a=A and math.sin(2*math.min(i,math.pi/4))or 0 local W=throttleEnabled local y=MAX_RPM-MAX_IDEAL_RPM local b=W and BASE_RPM+(BASE_RPM*math.min(F/MAX_SPEED,1.5)+a*y)or BASE_RPM local q=math.clamp(MAX_IDEAL_RPM+a*y,MAX_IDEAL_RPM,MAX_RPM)local Z=W and b+gainModifier*(q-b)or b local m=Z-enginePower local B=(a*ENGINE_GAIN_ACCEL)*3 local D=enginePower+(m*(W and ENGINE_GAIN_ACCEL+B or ENGINE_GAIN_DECCEL))*K enginePower=math.clamp(D,math.min(BASE_RPM,q),q)for N=1,#s,1 do local e=s[N]local M=e.Sound local K,d,n=e.RPM,e.MinRPM,e.MaxRPM local F if D>=d and D<=n then F=1 elseif D<d then F=1-(d-D)/(e.Crossover or RPM_CROSSOVER)else F=1-(D-n)/(e.Crossover or RPM_CROSSOVER)end F=F*V.engineSoundWeight local H=(D/K)*e.PitchModification M.Volume=F*e.Volume M.PlaybackSpeed=H end end end return V

local SLIDE_THRESHOLD = 0.6			-- The threshold at which skid-marks appear
local SLIDE_DEBOUNCE = 0.2			-- The minimum time to display skid-marks for
local SLIDE_MARK_OFFSET = 0.02		-- The studs to offset the skid-marks from the ground by

local defaultAudioData = { -- what to use if no custom sounds are provided
	{
		RPM = BASE_RPM,
		MinRPM = 0,
		MaxRPM = BASE_RPM + RPM_CROSSOVER,
		Volume = 3,
		PitchModification = 1,
		SoundID = "rbxassetid://5257533692",
	},
	{
		RPM = 3000,
		MinRPM = BASE_RPM + RPM_CROSSOVER,
		MaxRPM = 3500,
		Volume = 1,
		PitchModification = 1,
		SoundID = "rbxassetid://5257534962"--"rbxasset://sounds/Medium.ogg",
	},
	{
		RPM = 4000,
		MinRPM = 3500,
		MaxRPM = 9e9,
		Volume = 1,
		PitchModification = 1,
		SoundID = "rbxassetid://5257536258"--"rbxasset://sounds/High.ogg",
	},
}

local audio = {}

local Effects = {}
Effects.__index = Effects

function Effects.new(chassis, effectsFolder, topModel)
	local self = setmetatable({},Effects)

	self.ignore = topModel

	self.base = chassis:FindFirstChild("FloorPanel")
	self.attachmentContainer = self.base

	local suspensions = {}
	table.insert(suspensions, chassis:FindFirstChild("SuspensionFL"))
	table.insert(suspensions, chassis:FindFirstChild("SuspensionFR"))
	table.insert(suspensions, chassis:FindFirstChild("SuspensionRL"))
	table.insert(suspensions, chassis:FindFirstChild("SuspensionRR"))

	local function createSound(soundId)
		local sound = Instance.new("Sound")
		sound.Volume = 0
		sound.Looped = true
		sound.SoundId = soundId
		sound.Parent = topModel.PrimaryPart
		if not sound.IsLoaded then
			sound.Loaded:Wait()
		end
		return sound
	end
	
	local function generateAudioInfo(soundId, baseRPM, minRPM, maxRPM, volume, pitchMod)
		return {
			RPM = baseRPM,
			MinRPM = minRPM,
			MaxRPM = maxRPM,
			Volume = volume,
			PitchModification = pitchMod,
			SoundID = soundId,
			Sound = createSound(soundId)
		}
	end
	
	local function createSounds(audioInfo)
		local effects = topModel:FindFirstChild("Effects")
		local engineLowSound
		local engineBaseSound
		local engineHighSound 
		if effects then
			-- idle sound as baseline, Engine sound as next layer, and if there's another sound present, use that as the next higher pitched sound
			engineLowSound = effects:FindFirstChild("Idle")
			engineBaseSound = effects:FindFirstChild("Engine")
			engineHighSound = effects:FindFirstChild("EngineHigh")
		end
		
		if not audio[1] then
			if engineLowSound then
				audio[1] = generateAudioInfo(engineLowSound.SoundId, BASE_RPM, 0, BASE_RPM+RPM_CROSSOVER, engineLowSound.Volume, engineLowSound.PlaybackSpeed)
			else
				audio[1] = generateAudioInfo(defaultAudioData[1].SoundID, BASE_RPM, 0, BASE_RPM+RPM_CROSSOVER, defaultAudioData[1].Volume, defaultAudioData[1].PitchModification)
			end
		end
		
		if not audio[2] then
			if engineBaseSound then
				audio[2] = generateAudioInfo(engineBaseSound.SoundId, 3000, BASE_RPM+RPM_CROSSOVER, 3500, engineBaseSound.Volume, engineBaseSound.PlaybackSpeed)
			else
				audio[2] = generateAudioInfo(defaultAudioData[2].SoundID, 3000, BASE_RPM+RPM_CROSSOVER, 3500, defaultAudioData[2].Volume, defaultAudioData[2].PitchModification)
			end
		end
		
		if not audio[3] then
			if engineHighSound then
				audio[3] = generateAudioInfo(engineHighSound.SoundId, 4000, 3500, 9e9, engineHighSound.Volume, engineHighSound.PlaybackSpeed)
			else
				audio[2].MaxRPM = 9e9
			end
		end
	end

	local function createWheelData(wheelPart)
		local attCenter = Instance.new("Attachment")
		attCenter.Name = "EffectsCenter"
		attCenter.Parent = self.attachmentContainer

		local attRight = Instance.new("Attachment")
		attRight.Name = "EffectsR"
		attRight.Parent = self.attachmentContainer

		local attLeft = Instance.new("Attachment")
		attLeft.Name = "EffectsL"
		attLeft.Parent = self.attachmentContainer

		local trail = nil
		local trailPrototype = effectsFolder:FindFirstChild("TireTrail")
		if trailPrototype and trailPrototype:IsA("Trail") then
			trail = trailPrototype:Clone()
			trail.Parent = self.attachmentContainer
			trail.Attachment0 = attLeft
			trail.Attachment1 = attRight
		end
		
		local wheelData = {
			wheel = wheelPart,
			attCenter = attCenter,
			attRight = attRight,
			attLeft = attLeft,
			trail = trail,
			lastContact = 0,
		}
		
		return wheelData
	end

	self.wheels = {}
	for _, suspension in ipairs(suspensions) do
		local wheelPart = suspension:FindFirstChild("Wheel")
		if wheelPart then
			table.insert(self.wheels, createWheelData(wheelPart))
		end
	end
	
	if #self.wheels == 0 then -- probably two-wheeler
		local children = chassis:GetChildren()
		for i = 1, #children do
			if children[i].Name == "Wheel" then
				table.insert(self.wheels, createWheelData(children[i]))
			end
		end
	end
	
	-- connect remote event
	local vehicleSeat = Vehicle:WaitForChild("Chassis"):WaitForChild("VehicleSeat")
	SetThrottleConnection = SetThrottleRemote.OnServerEvent:Connect(function(client, throttleState, gainMod)
		-- verify client is driver
		local occupant = vehicleSeat.Occupant
		if occupant.Parent == client.Character then
			self:SetThrottleEnabled(throttleState, gainMod)
		end
	end)
	
	-- create sounds based off of what we have in the effects folder
	createSounds()
	
	local ignitionSound = effectsFolder:FindFirstChild("EngineStart")
	if ignitionSound then
		self.ignitionMaxVolume = ignitionSound.Volume
		self.ignitionSound = ignitionSound:Clone()
		self.ignitionSound.Parent = chassis.PrimaryPart
	end
	
	local stopSound = effectsFolder:FindFirstChild("EngineStop")
	if stopSound then
		self.stopSound = stopSound:Clone()
		self.stopSound.Parent = chassis.PrimaryPart
	end
	
	local accelerateSound = effectsFolder:FindFirstChild("Accelerate")
	if accelerateSound then
		self.accelerateSoundVolume = accelerateSound.Volume
		self.accelerateSoundWeight = 0
		self.accelerateSound = accelerateSound:Clone()
		self.accelerateSound.Parent = chassis.PrimaryPart
	end
	self.engineSoundWeight = 1

	self.igniting = false
	self.throttle = 0
	self.slideSpeed = 0
	self.disableTime = 0
	self.active = false

	return self
end

function Effects:Enable()
	self.active = true
	if #self.wheels > 0 then
		self.disableTime = 0
		if self.heartbeatConn then
			self.heartbeatConn:Disconnect()
		end
		self.heartbeatConn = RunService.Heartbeat:Connect(function(dt)
			self:OnHeartbeat(dt)
		end)
		
		if self.ignitionSound and not self.igniting then
			self.igniting = true
			coroutine.wrap(function()
				if EngineSoundEnabled then
					self.ignitionSound.Volume = self.ignitionMaxVolume
					self.ignitionSound:Play()
					repeat
						RunService.Stepped:Wait()
					until not (self.igniting and self.ignitionSound.IsPlaying)
				end
				self.igniting = false
			end)()
		end
		
		for i = 1, #audio do
			audio[i].Sound:Play()
		end
	end
end

function Effects:DisableInternal()
	self.active = false
	if self.heartbeatConn then
		self.heartbeatConn:Disconnect()
	end
	self.heartbeatConn = nil

	-- Disable sounds
	for i = 1, #audio do
		if audio[i].Sound then
			audio[i].Sound:Stop()
		end
	end
	
	if self.stopSound then self.stopSound:Play() end

	if #self.wheels > 0 then
		for _,wheelData in ipairs(self.wheels) do
			wheelData.trail.Enabled = false
		end
	end

	self.disableTime = 0
end

function Effects:Disable()
	-- Request effects to be disabled soon (upon driver exiting vehicle)
	if self.disableTime == 0 then
		self.disableTime = tick() + EFFECTS_GRACE_PERIOD
	end
end

function Effects:SetThrottleEnabled(toggle, gainMod)
	if not EngineSoundEnabled then return end
	
	-- sets whether RPM is building up or not for the engine. Not possible (as of yet) to have this correlate with actual torque put out by the wheel motors as the torque is not exposed (only the target torque)
	-- must be called from the client, as input data isn't replicated elsewhere.
	
	gainMod = gainMod or 1
	gainModifier = gainMod
	
	if RunService:IsClient() then
		-- keep track of throttle state as to not spam the server with events
		if toggle ~= throttleEnabled or tick()-lastThrottleUpdate > 1/THROTTLE_UPDATE_RATE then
			lastThrottleUpdate = tick()
			SetThrottleRemote:FireServer(toggle, gainMod)
			throttleEnabled = toggle
		end	
	else
		if self.active then
			throttleEnabled = toggle
		else
			throttleEnabled = false
		end
	end
end

function Effects:OnHeartbeat(dt)
	if self.ignore.Parent == nil then
		return
	end
	
	if self.disableTime > 0 and tick() > self.disableTime then
		self:DisableInternal()
		return
	end
	
	local hasGroundContact = true -- assume ground contact 
	
	if TireTrailEnabled then
		for _, wheelData in ipairs(self.wheels) do
			
			local wheel = wheelData.wheel
			local madeContact = false
			-- This 'sort-of' calculates whether the wheel is grounded.
			for _, basePart in ipairs(wheel:GetTouchingParts()) do
				if not basePart:IsDescendantOf(self.ignore) then
					wheelData.lastContact = tick()
					madeContact = true
					break
				end
			end
			
			hasGroundContact = madeContact

			if tick() - wheelData.lastContact <= SLIDE_DEBOUNCE then

				local radius = wheel.Size.Y / 2
				local width = wheel.Size.X / 2
				
				local wheelLeftCFrame = wheel.CFrame * CFrame.new(width, 0, 0) - Vector3.new(0, radius - SLIDE_MARK_OFFSET, 0)
				local wheelRightCFrame = wheel.CFrame * CFrame.new(-width, 0, 0) - Vector3.new(0, radius - SLIDE_MARK_OFFSET, 0)
				
				wheelData.attRight.WorldPosition = wheelRightCFrame.p
				wheelData.attLeft.WorldPosition =  wheelLeftCFrame.p
				
				-- RotationalVelocity: Speed at the edge of the wheel from it rotating
				-- HorizontalVelocity: Speed the wheel is actually moving at
				-- SlideSpeed: The speed at which the wheel is sliding relative to its rotational velocity
				local rotationalVelocity = radius * (self.base.CFrame:VectorToObjectSpace(wheel.RotVelocity)).X
				local horizontalVelocity = self.base.CFrame:VectorToObjectSpace(wheel.Velocity).Z
				local slideSpeed = math.abs(rotationalVelocity - horizontalVelocity)
				
				local slipValue = slideSpeed / math.abs(rotationalVelocity)
				local sliding = slipValue >= SLIDE_THRESHOLD

				wheelData.trail.Enabled = sliding

			else
				wheelData.trail.Enabled = false
			end
			
		end
	end
	
	--Engine Sounds
	if EngineSoundEnabled then
		local constraints = self.ignore.Constraints:GetChildren()
		local main = self.ignore.PrimaryPart
		if not main then return end -- car probably fell off the map
		
		local function getAvgAngularSpeed() 
			-- use the average angular speed of the wheels to guess how much torque is being generated by the engine
			-- note: cannot get actual motor torque as a limitation of the roblox engine, thus this can't be completely accurate
			-- when car is going uphill, there will be a noticeable difference.
			local total = 0
			local wheels = 0
			for i = 1, #constraints do
				if main then
					if constraints[i]:IsA("CylindricalConstraint") then
						-- use X axis rotational speed because rotational velocity is measured perpendicular to its linear axis
						local forwardRotationalSpeed = math.abs(main.CFrame:vectorToObjectSpace(constraints[i].Attachment1.Parent.RotVelocity).X)
						wheels = wheels+1
						total = total+forwardRotationalSpeed
					end
				end
			end
			return total/wheels
		end

		if throttleEnabled and self.igniting then
			self.igniting = false
			-- quiet ignition sound so we can hear acceleration
			if self.ignitionSound then
				local tween = TweenService:Create(self.ignitionSound, engineStartTween, {Volume = 1})
				tween:Play()
			end
		end
		
		-- try to figure out a good weight for accleration sound
		local weight = 1
		if throttleEnabled then
			--print("enginePower", enginePower)
			if enginePower <= BASE_RPM+100 then
				if not self.accelerateSound.Playing then
					-- TODO: Determine why/how this is getting stuck on :RS
					-- self.accelerateSound:Play()
				end
			end

			if self.accelerateSound.Playing then
				local baseThreshold = BASE_RPM+400
				local blendThreshold = BASE_RPM+600

				if enginePower <= baseThreshold then
					weight = 0
					self.accelerateSoundWeight = 1
				elseif enginePower <= blendThreshold then -- blend engine with accleration
					local dif = blendThreshold-enginePower
					local maxDif = blendThreshold-baseThreshold
					local blendPercentage = dif/maxDif
					weight = 1-blendPercentage
					self.accelerateSoundWeight = blendPercentage
					--print(weight, self.accelerateSoundWeight)
				else -- end sound
					self.accelerateSoundWeight = 0
					if self.accelerateSound.Playing then
						self.accelerateSound:Stop()
					end
				end
			end
		end

		self.accelerateSound.Volume = self.accelerateSoundVolume*self.accelerateSoundWeight
		self.engineSoundWeight = weight

		local targetAngularSpeed = math.abs(self.ignore.Constraints.MotorFL.AngularVelocity)
		local currentAngularSpeed = getAvgAngularSpeed()
		
		local forwardVector = main.CFrame.LookVector
		local forwardFlatVector = Vector3.new(forwardVector.x, 0, forwardVector.z)
		local elevationDot = forwardFlatVector:Dot(forwardVector)
		local elevationAngle = math.acos(math.clamp(elevationDot, -1, 1)) -- not sure how that went over 1
		
		-- add to the engine power by a factor of the sine of the elevation angle to make going uphill require more power
		local movingAgainstGravity = main.Velocity.Y > 0
		local gravityEffect = movingAgainstGravity and math.sin(2*math.min(elevationAngle,math.pi/4)) or 0
		
		local accelerating = throttleEnabled
		
		local maxDif = MAX_RPM-MAX_IDEAL_RPM
		
		local baseRPMFromThrottle = (accelerating and BASE_RPM+(BASE_RPM*math.min(currentAngularSpeed/MAX_SPEED,1.5) + gravityEffect*maxDif) or BASE_RPM) 
		local maxRPM = math.clamp(MAX_IDEAL_RPM + gravityEffect*maxDif, MAX_IDEAL_RPM, MAX_RPM)
		local targetRPM = accelerating and baseRPMFromThrottle + gainModifier*(maxRPM-baseRPMFromThrottle) or baseRPMFromThrottle

		local dif = targetRPM-enginePower

		local inclineGainEffect = gravityEffect*ENGINE_GAIN_ACCEL*3
		local currentRPM = enginePower + dif*(accelerating and ENGINE_GAIN_ACCEL+inclineGainEffect or ENGINE_GAIN_DECCEL)*dt
		enginePower = math.clamp(currentRPM, math.min(BASE_RPM,maxRPM), maxRPM)

		for i = 1, #audio do
			
			local audioInfo = audio[i]
			local sound = audioInfo.Sound
			
			local baseRPM, minRPM, maxRPM = audioInfo.RPM, audioInfo.MinRPM, audioInfo.MaxRPM
			local volume
			
			if currentRPM >= minRPM and currentRPM <= maxRPM then
				volume = 1
			elseif currentRPM < minRPM then
				volume = 1 - ((minRPM - currentRPM) / (audioInfo.Crossover or RPM_CROSSOVER))
			else
				volume = 1 - ((currentRPM - maxRPM) / (audioInfo.Crossover or RPM_CROSSOVER))
			end
			volume = volume*self.engineSoundWeight
			
			local playbackSpeed = (currentRPM / baseRPM) * audioInfo.PitchModification
			
			sound.Volume = volume*audioInfo.Volume
			sound.PlaybackSpeed = playbackSpeed
		end
	end
end

return Effects
