AddCSLuaFile() ENT.Base = "lvs_wheeldrive_engine" DEFINE_BASECLASS( "lvs_wheeldrive_engine" ) ENT.Spawnable = false ENT.AdminSpawnable = false ENT.DoNotDuplicate = true ENT._LVS = true ENT.RenderGroup = RENDERGROUP_BOTH if SERVER then function ENT:Think() return false end return end ENT._oldEnActive = false ENT._ActiveSounds = {} function ENT:StopSounds() for id, sound in pairs( self._ActiveSounds ) do if istable( sound ) then for _, snd in pairs( sound ) do if snd then snd:Stop() end end else sound:Stop() end self._ActiveSounds[ id ] = nil end end function ENT:HandleEngineSounds( vehicle ) local ply = LocalPlayer() local pod = ply:GetVehicle() local Throttle = (vehicle:GetThrottle() - vehicle:GetThrustStrenght() * vehicle:GetThrottle() * 0.5) + vehicle:GetBrake() local Doppler = vehicle:CalcDoppler( ply ) local DrivingMe = ply:lvsGetVehicle() == vehicle local VolumeSetNow = false local FirstPerson = false if IsValid( pod ) then local ThirdPerson = pod:GetThirdPersonMode() if ThirdPerson ~= self._lvsoldTP then self._lvsoldTP = ThirdPerson VolumeSetNow = DrivingMe end FirstPerson = DrivingMe and not ThirdPerson end if DrivingMe ~= self._lvsoldDrivingMe then self._lvsoldDrivingMe = DrivingMe self:StopSounds() self._oldEnActive = nil return end local FT = RealFrameTime() self._smTHR = self._smTHR and self._smTHR + (Throttle - self._smTHR) * FT or 0 local HasActiveSoundEmitters = false if DrivingMe then HasActiveSoundEmitters = vehicle:HasActiveSoundEmitters() end local rpmSet = false for id, sound in pairs( self._ActiveSounds ) do if not sound then continue end local data = self.EngineSounds[ id ] local Pitch = math.Clamp( data.Pitch + self._smTHR * data.PitchMul, data.PitchMin, data.PitchMax ) local PitchMul = data.UseDoppler and Doppler or 1 local InActive = self._smTHR > data.FadeOut or self._smTHR < data.FadeIn if data.FadeOut >= 1 and self._smTHR > 1 then InActive = false end local Volume = InActive and 0 or LVS.EngineVolume if data.VolumeMin and data.VolumeMax and not InActive then Volume = math.max(self._smTHR - data.VolumeMin,0) / (1 - data.VolumeMin) * data.VolumeMax * LVS.EngineVolume end if istable( sound ) then sound.ext:ChangePitch( math.Clamp( Pitch * PitchMul, 0, 255 ), 0.2 ) if sound.int then sound.int:ChangePitch( math.Clamp( Pitch, 0, 255 ), 0.2 ) end local fadespeed = VolumeSetNow and 0 or data.FadeSpeed if FirstPerson then sound.ext:ChangeVolume( 0, 0 ) if HasActiveSoundEmitters then Volume = Volume * 0.25 fadespeed = fadespeed * 0.5 end if sound.int then sound.int:ChangeVolume( Volume, fadespeed ) end else if HasActiveSoundEmitters then Volume = Volume * 0.75 fadespeed = fadespeed * 0.5 end sound.ext:ChangeVolume( Volume, fadespeed ) if sound.int then sound.int:ChangeVolume( 0, 0 ) end end else if data.FadeInRestart then if Volume == 0 then if sound:GetVolume() == 0 and sound:IsPlaying() then sound:Stop() else sound:ChangePitch( math.Clamp( Pitch * PitchMul, 0, 255 ), 0.2 ) sound:ChangeVolume( Volume, data.FadeSpeed ) end else if sound:IsPlaying() then sound:ChangePitch( math.Clamp( Pitch * PitchMul, 0, 255 ), 0.2 ) sound:ChangeVolume( Volume, data.FadeSpeed ) else sound:PlayEx( Volume, math.Clamp( Pitch * PitchMul, 0, 255 ) ) end end else sound:ChangePitch( math.Clamp( Pitch * PitchMul, 0, 255 ), 0.2 ) sound:ChangeVolume( Volume, data.FadeSpeed ) end if not rpmSet then rpmSet = true self:SetRPM( vehicle.EngineIdleRPM + ((sound:GetPitch() - data.Pitch) / data.PitchMul) * (vehicle.EngineMaxRPM - vehicle.EngineIdleRPM) ) end end end end function ENT:OnEngineActiveChanged( Active ) if not Active then self:StopSounds() return end local ply = LocalPlayer() local ViewPos = ply:GetViewEntity():GetPos() local veh = ply:lvsGetVehicle() local Base = self:GetBase() for id, data in pairs( self.EngineSounds ) do if not isstring( data.sound ) then continue end self.EngineSounds[ id ].Pitch = data.Pitch or 80 self.EngineSounds[ id ].PitchMin = data.PitchMin or 0 self.EngineSounds[ id ].PitchMax = data.PitchMax or 255 self.EngineSounds[ id ].PitchMul = data.PitchMul or 100 self.EngineSounds[ id ].UseDoppler = data.UseDoppler ~= false self.EngineSounds[ id ].FadeIn = data.FadeIn or 0 self.EngineSounds[ id ].FadeOut = data.FadeOut or 1 self.EngineSounds[ id ].FadeSpeed = data.FadeSpeed or 1.5 self.EngineSounds[ id ].SoundLevel = data.SoundLevel or 85 if data.sound_int and data.sound_int ~= data.sound then local sound = CreateSound( self, data.sound ) sound:SetSoundLevel( data.SoundLevel ) sound:PlayEx(0,100) if data.sound_int == "" then self._ActiveSounds[ id ] = { ext = sound, int = false, } else local sound_interior = CreateSound( self, data.sound_int ) sound_interior:SetSoundLevel( data.SoundLevel ) sound_interior:PlayEx(0,100) self._ActiveSounds[ id ] = { ext = sound, int = sound_interior, } end else local sound = CreateSound( self, data.sound ) sound:SetSoundLevel( data.SoundLevel ) sound:PlayEx(0,100) self._ActiveSounds[ id ] = sound end end end function ENT:Think() local vehicle = self:GetBase() if not IsValid( vehicle ) then return end self:DamageFX( vehicle ) self:EngineFX( vehicle ) if not self.EngineSounds then self.EngineSounds = vehicle.EngineSounds return end local EngineActive = vehicle:GetEngineActive() if self._oldEnActive ~= EngineActive then self._oldEnActive = EngineActive self:OnEngineActiveChanged( EngineActive ) end if EngineActive then self:HandleEngineSounds( vehicle ) self:ExhaustFX( vehicle ) else self._smTHR = 0 end end function ENT:EngineFX( vehicle ) if not vehicle:GetEngineActive() then return end local EntTable = vehicle:GetTable() if not EntTable.EngineSplash then return end local T = CurTime() if (EntTable.nextPropFX or 0) > T then return end local throttle = (self:GetRPM() / EntTable.EngineMaxRPM) EntTable.nextPropFX = T + math.max(0.2 - throttle,0.05) local startpos = self:GetPos() local endpos = self:LocalToWorld( Vector(0,0,-EntTable.EngineSplashDistance) ) local traceWater = util.TraceLine( { start = startpos, endpos = endpos, mask = MASK_WATER, filter = vehicle:GetCrosshairFilterEnts() } ) if not traceWater.Hit then return end local pos = traceWater.HitPos local emitter = vehicle:GetParticleEmitter( pos ) if not IsValid( emitter ) then return end local LightColor = render.GetLightColor( pos ) local VecCol = Vector(1,1.2,1.4) * (0.06 + (0.2126 * LightColor.r) + (0.7152 * LightColor.g) + (0.0722 * LightColor.b)) * 1000 VecCol.x = math.min( VecCol.x, 255 ) VecCol.y = math.min( VecCol.y, 255 ) VecCol.z = math.min( VecCol.z, 255 ) local particle = emitter:Add( "effects/splash4", pos ) local dir = self:LocalToWorldAngles( Angle(EntTable.EngineSplashThrowAngle,180 - vehicle:GetSteer() * 30,0) ):Forward() local vel = (VectorRand() * EntTable.EngineSplashVelocityRandomAdd + dir * EntTable.EngineSplashVelocity) * throttle particle:SetVelocity( vel ) particle:SetDieTime( 1 ) particle:SetAirResistance( 10 ) particle:SetStartAlpha( 255 ) particle:SetStartSize( EntTable.EngineSplashStartSize * throttle ) particle:SetEndSize( EntTable.EngineSplashEndSize * throttle ) particle:SetRoll( math.Rand(-5,5) ) particle:SetColor(VecCol.r,VecCol.g,VecCol.b) particle:SetGravity( Vector(0,0,-600) ) particle:SetCollide( false ) particle:SetNextThink( T ) particle:SetThinkFunction( function( p ) p:SetNextThink( CurTime() ) local fxpos = p:GetPos() if fxpos.z > pos.z then return end p:SetDieTime( 0 ) local startpos = Vector(fxpos.x,fxpos.y,pos.z + 1) if not IsValid( vehicle ) then return end local emitter3D = vehicle:GetParticleEmitter3D( vehicle:GetPos() ) if not IsValid( emitter3D ) then return end local particle = emitter3D:Add("effects/splashwake1", startpos ) if not particle then return end local scale = math.Rand(0.5,2) local size = p:GetEndSize() local vsize = Vector(size,size,size) particle:SetStartSize( size * scale * 0.5 ) particle:SetEndSize( size * scale ) particle:SetDieTime( math.Rand(0.5,1) ) particle:SetStartAlpha( 255 ) particle:SetEndAlpha( 0 ) particle:SetPos( startpos ) particle:SetAngles( Angle(-90,math.Rand(-180,180),0) ) particle:SetColor(VecCol.r,VecCol.g,VecCol.b) end ) end