--Apply Power

function Engine()
	--Horsepower Curve

	local HP=_Tune2.Horsepower.Value/100
	local HP_B=((_Tune2.Horsepower.Value*((_TPsi)*(_Tune.CompressRatio/10))/7.5)/2)/100
	local Peak=_Tune.PeakRPM/1000
	local Sharpness=_Tune.PeakSharpness
	local CurveMult=_Tune.CurveMult
	local EQ=_Tune.EqPoint/1000

	--Horsepower Curve

	function curveHP(RPM)
		RPM=RPM/1000
		return ((-(RPM-Peak)^2)*math.min(HP/(Peak^2),CurveMult^(Peak/HP))+HP)*(RPM-((RPM^Sharpness)/(Sharpness*Peak^(Sharpness-1))))
	end

	local PeakCurveHP = curveHP(_Tune.PeakRPM)

	function curvePSI(RPM)
		RPM=RPM/1000
		return ((-(RPM-Peak)^2)*math.min(HP_B/(Peak^2),CurveMult^(Peak/HP_B))+HP_B)*(RPM-((RPM^Sharpness)/(Sharpness*Peak^(Sharpness-1))))
	end
	local PeakCurvePSI = curvePSI(_Tune.PeakRPM)

	--Plot Current Horsepower
	function GetCurve(x,gear)
		local hp=(math.max(curveHP(x)/(PeakCurveHP/HP),0))*100
		return hp,((hp*(EQ/x))*_Tune.Ratios[gear+2]*fFD*hpScaling)*1000
	end

	--Plot Current Boost (addition to Horsepower)
	function GetPsiCurve(x,gear)
		local hp=(math.max(curvePSI(x)/(PeakCurvePSI/HP_B),0))*100
		return hp,((hp*(EQ/x))*_Tune.Ratios[gear+2]*fFD*hpScaling)*1000
	end	

	--Output Cache	
	local HPCache = {}
	local PSICache = {}

	for gear,ratio in pairs(_Tune.Ratios) do
		local nhpPlot = {}
		local bhpPlot = {}
		for rpm = math.floor(_Tune.IdleRPM/100),math.ceil((_Tune.Redline+100)/100) do
			local ntqPlot = {}
			local btqPlot = {}
			ntqPlot.Horsepower,ntqPlot.Torque = GetCurve(rpm*100,gear-2)
			btqPlot.Horsepower,btqPlot.Torque = GetPsiCurve(rpm*100,gear-2)
			hp1,tq1 = GetCurve((rpm+1)*100,gear-2)
			hp2,tq2 = GetPsiCurve((rpm+1)*100,gear-2)
			ntqPlot.HpSlope = (hp1 - ntqPlot.Horsepower)
			btqPlot.HpSlope = (hp2 - btqPlot.Horsepower)
			ntqPlot.TqSlope = (tq1 - ntqPlot.Torque)
			btqPlot.TqSlope = (tq2 - btqPlot.Torque)
			nhpPlot[rpm] = ntqPlot
			bhpPlot[rpm] = btqPlot
		end
		table.insert(HPCache,nhpPlot)
		table.insert(PSICache,bhpPlot)
	end
	--Neutral Gear
	if _CGear==0 then _ClutchOn = false end

	--Car Is Off
	local revMin = _Tune.IdleRPM
	if not _IsOn then 
		revMin = 0 
		_CGear = 0
		_ClutchOn = false
		_GThrot = _Tune.IdleThrottle/100
	end

	--Determine RPM
	local maxSpin=0
	local maxCount=0
	for i,v in pairs(Drive) do maxSpin = maxSpin + v.RotVelocity.Magnitude maxCount = maxCount + 1 end
	maxSpin=maxSpin/maxCount

	if _ClutchOn then
		local aRPM = math.max(math.min(maxSpin*_Tune.Ratios[_CGear+2]*fFDr,_Tune.Redline+100),revMin)
		local clutchP = math.min(math.abs(aRPM-_RPM)/_Tune.ClutchTol,.9)
		_RPM = _RPM*clutchP  +  aRPM*(1-clutchP)
	else
		if _GThrot-(_Tune.IdleThrottle/100)>0 then
			if _RPM>_Tune.Redline then
				_RPM = _RPM-_Tune.RevBounce*2
			else
				_RPM = math.min(_RPM+_Tune.RevAccel*_GThrot,_Tune.Redline+100)
			end
		else
			_RPM = math.max(_RPM-_Tune.RevDecay,revMin)
		end
	end

	--Rev Limiter
	_spLimit = (_Tune.Redline+100)/(fFDr*_Tune.Ratios[_CGear+2])
	if _RPM>_Tune.Redline then 
		if _CGear<#_Tune.Ratios-2 then
			_RPM = _RPM-_Tune.RevBounce
		else
			_RPM = _RPM-_Tune.RevBounce*.5
		end
	end

	local TPsi = _TPsi/_TCount

	if _Tune.Aspiration ~= "Super" then 
		local HP2_B=((_Tune2.Horsepower.Value*((_TPsi)*(_Tune.CompressRatio/10))/7.5)/2)/100
		_Boost = _Boost + ((((((_HP*(_GThrot*1.2)/_Tune2.Horsepower.Value)/8)-(((_Boost/TPsi*(TPsi/15)))))*((8/_Tune.TurboSize)*2))/TPsi)*15)
		if _Boost < 0.05 then _Boost = 0.05 elseif _Boost > 2 then _Boost = 2 end
	else
		if _GThrot>sthrot then
			sthrot=math.min(_GThrot,sthrot+_Tune.Sensitivity)
		elseif _GThrot<sthrot then
			sthrot=math.max(_GThrot,sthrot-_Tune.Sensitivity)
		end
		_Boost = (_RPM/_Tune.Redline)*(.5+(1.5*sthrot))
	end


	local cTq = HPCache[_CGear+2][math.floor(math.min(_Tune.Redline,math.max(_Tune.IdleRPM,_RPM))/100)]
	_NH = cTq.Horsepower+(cTq.HpSlope*((_RPM-math.floor(_RPM/100))/1000)%1)
	_NT = cTq.Torque+(cTq.TqSlope*((_RPM-math.floor(_RPM/100))/1000)%1)
	if _Tune.Aspiration ~= "Natural" then
		local bTq = PSICache[_CGear+2][math.floor(math.min(_Tune.Redline,math.max(_Tune.IdleRPM,_RPM))/100)]
		_BH = bTq.Horsepower+(bTq.HpSlope*((_RPM-math.floor(_RPM/100))/1000)%1)
		_BT = bTq.Torque+(bTq.TqSlope*((_RPM-math.floor(_RPM/100))/1000)%1)
		_HP = _NH + (_BH*(_Boost/2))
		_OutTorque = _NT + (_BT*(_Boost/2))
	else
		_HP = _NH
		_OutTorque = _NT
	end


	local iComp =(car.DriveSeat.CFrame.lookVector.y)*cGrav
	if _CGear==-1 then iComp=-iComp end
	_OutTorque = _OutTorque*math.max(1,(1+iComp))

	--Average Rotational Speed Calculation
	local fwspeed=0
	local fwcount=0
	local rwspeed=0
	local rwcount=0

	for i,v in pairs(car.Wheels:GetChildren()) do
		if v.Name=="FL" or v.Name=="FR" or v.Name == "F" then
			fwspeed=fwspeed+v.RotVelocity.Magnitude
			fwcount=fwcount+1
		elseif v.Name=="RL" or v.Name=="RR" or v.Name == "R" then
			rwspeed=rwspeed+v.RotVelocity.Magnitude
			rwcount=rwcount+1
		end
	end
	fwspeed=fwspeed/fwcount
	rwspeed=rwspeed/rwcount	
	local cwspeed=(fwspeed+rwspeed)/2

	--Update Wheels
	for i,v in pairs(car.Wheels:GetChildren()) do
		--Reference Wheel Orientation
		local Ref=(CFrame.new(v.Position-((v.Arm.CFrame*cfWRot).lookVector),v.Position)*cfYRot).lookVector
		local aRef=1
		local diffMult=1
		if v.Name=="FL" or v.Name=="RL" then aRef=-1 end

		--Differential/Torque-Vectoring
		if v.Name=="FL" or v.Name=="FR" then
			diffMult=math.max(0,math.min(1,1+((((v.RotVelocity.Magnitude-fwspeed)/fwspeed)/(math.max(_Tune.FDiffSlipThres,1)/100))*((_Tune.FDiffLockThres-50)/50))))
			if _Tune.Config == "AWD" then
				diffMult=math.max(0,math.min(1,diffMult*(1+((((fwspeed-cwspeed)/cwspeed)/(math.max(_Tune.CDiffSlipThres,1)/100))*((_Tune.CDiffLockThres-50)/50)))))
			end
		elseif v.Name=="RL" or v.Name=="RR" then
			diffMult=math.max(0,math.min(1,1+((((v.RotVelocity.Magnitude-rwspeed)/rwspeed)/(math.max(_Tune.RDiffSlipThres,1)/100))*((_Tune.RDiffLockThres-50)/50))))
			if _Tune.Config == "AWD" then
				diffMult=math.max(0,math.min(1,diffMult*(1+((((rwspeed-cwspeed)/cwspeed)/(math.max(_Tune.CDiffSlipThres,1)/100))*((_Tune.CDiffLockThres-50)/50)))))
			end
		end

		_TCSActive = false
		_ABSActive = false
		--Output
		if _PBrake and (v.Name=="RR" or v.Name=="RL") then
			--PBrake
			v["#AV"].maxTorque=Vector3.new(math.abs(Ref.x),math.abs(Ref.y),math.abs(Ref.z))*PBrakeForce
			v["#AV"].angularvelocity=Vector3.new()
		else
			--Apply Power
			if _GBrake==0 then
				local driven = false
				for _,a in pairs(Drive) do if a==v then driven = true end end
				if driven then
					local on=1
					if not script.Parent.IsOn.Value then on=0 end
					local clutch=1
					if not _ClutchOn then clutch=0 end
					local throt = _GThrot * _GThrotShift
					local tq = _OutTorque

					--Apply AWD Vectoring
					if _Tune.Config == "AWD" then
						local bias = (_Tune.TorqueVector+1)/2
						if v.Name=="FL" or v.Name=="FR" then
							tq = tq*(1-bias)
						elseif v.Name=="RL" or v.Name=="RR" then
							tq = tq*bias
						end
					end

					--Apply TCS
					tqTCS = 1
					if _TCS then
						tqTCS = 1-(math.min(math.max(0,math.abs(v.RotVelocity.Magnitude*(v.Size.x/2) - v.Velocity.Magnitude)-_Tune.TCSThreshold)/_Tune.TCSGradient,1)*(1-(_Tune.TCSLimit/100)))
					end
					if tqTCS < 1 then
						_TCSAmt = tqTCS
						_TCSActive = true
					end

					--Update Forces
					local dir=1
					if _CGear==-1 then dir = -1 end
					v["#AV"].maxTorque=Vector3.new(math.abs(Ref.x),math.abs(Ref.y),math.abs(Ref.z))*tq*(1+(v.RotVelocity.Magnitude/60)^1.15)*throt*tqTCS*diffMult*on*clutch
					v["#AV"].angularvelocity=Ref*aRef*_spLimit*dir
				else
					v["#AV"].maxTorque=Vector3.new()
					v["#AV"].angularvelocity=Vector3.new()
				end

				--Brakes
			else
				local brake = _GBrake

				--Apply ABS
				local tqABS = 1
				if _ABS and math.abs(v.RotVelocity.Magnitude*(v.Size.x/2) - v.Velocity.Magnitude)-_Tune.ABSThreshold>0 then
					tqABS = 0
				end
				_ABSActive = (tqABS<1)

				--Update Forces
				if v.Name=="FL" or v.Name=="FR" or v.Name=="F" then
					v["#AV"].maxTorque=Vector3.new(math.abs(Ref.x),math.abs(Ref.y),math.abs(Ref.z))*FBrakeForce*brake*tqABS
				else
					v["#AV"].maxTorque=Vector3.new(math.abs(Ref.x),math.abs(Ref.y),math.abs(Ref.z))*RBrakeForce*brake*tqABS
				end
				v["#AV"].angularvelocity=Vector3.new()
			end
		end
	end
end
