add sborka

This commit is contained in:
2026-03-31 10:27:04 +03:00
commit f5e5f56c84
2345 changed files with 382127 additions and 0 deletions

View File

@@ -0,0 +1,75 @@
function ENT:CalcViewOverride( ply, pos, angles, fov, pod )
return pos, angles, fov
end
function ENT:CalcViewDirectInput( ply, pos, angles, fov, pod )
return LVS:CalcView( self, ply, pos, angles, fov, pod )
end
function ENT:CalcViewMouseAim( ply, pos, angles, fov, pod )
return LVS:CalcView( self, ply, pos, angles, fov, pod )
end
function ENT:CalcViewDriver( ply, pos, angles, fov, pod )
pos = pos + pod:GetUp() * 7 - pod:GetRight() * 11
if ply:lvsMouseAim() then
angles = ply:EyeAngles()
return self:CalcViewMouseAim( ply, pos, angles, fov, pod )
else
return self:CalcViewDirectInput( ply, pos, angles, fov, pod )
end
end
function ENT:CalcViewPassenger( ply, pos, angles, fov, pod )
return LVS:CalcView( self, ply, pos, angles, fov, pod )
end
function ENT:LVSCalcView( ply, original_pos, original_angles, original_fov, pod )
local pos, angles, fov = self:CalcViewOverride( ply, original_pos, original_angles, original_fov, pod )
local new_fov = math.min( fov + self:CalcViewPunch( ply, pos, angles, fov, pod ), 180 )
if self:GetDriverSeat() == pod then
return self:CalcViewDriver( ply, pos, angles, new_fov, pod )
else
return self:CalcViewPassenger( ply, pos, angles, new_fov, pod )
end
end
function ENT:SuppressViewPunch( time )
self._viewpunch_supressed_time = CurTime() + (time or 0.2)
end
function ENT:IsViewPunchSuppressed()
return (self._viewpunch_supressed_time or 0) > CurTime()
end
function ENT:CalcViewPunch( ply, pos, angles, fov, pod )
local Vel = self:GetVelocity()
local VelLength = Vel:Length()
local VelPercentMaxSpeed = math.min( VelLength / 1000, 1 )
if ply:lvsMouseAim() then
angles = ply:EyeAngles()
end
local direction = (90 - self:AngleBetweenNormal( angles:Forward(), Vel:GetNormalized() )) / 90
local FovValue = math.min( VelPercentMaxSpeed ^ 2 * 100, 15 )
local Throttle = self:GetThrottle()
local Brake = self:GetBrake()
if self:IsViewPunchSuppressed() then
self._viewpunch_fov = self._viewpunch_fov and self._viewpunch_fov + (-VelPercentMaxSpeed * FovValue - self._viewpunch_fov) * RealFrameTime() or 0
else
local newFov =(1 - VelPercentMaxSpeed) * Throttle * FovValue - VelPercentMaxSpeed * Brake * FovValue
self._viewpunch_fov = self._viewpunch_fov and self._viewpunch_fov + (newFov - self._viewpunch_fov) * RealFrameTime() * 10 or 0
end
return self._viewpunch_fov * (90 - self:AngleBetweenNormal( angles:Forward(), Vel:GetNormalized() )) / 90
end

View File

@@ -0,0 +1,112 @@
function ENT:IsBackFireEnabled()
if not isfunction( self.GetBackfire ) then return false end
return self:GetBackfire()
end
function ENT:DoExhaustFX( Magnitude )
for _, data in ipairs( self.ExhaustPositions ) do
if data.bodygroup then
if not self:BodygroupIsValid( data.bodygroup.name, data.bodygroup.active ) then continue end
end
local effectdata = EffectData()
effectdata:SetOrigin( self:LocalToWorld( data.pos ) )
effectdata:SetNormal( self:LocalToWorldAngles( data.ang ):Forward() )
effectdata:SetMagnitude( Magnitude )
effectdata:SetEntity( self )
util.Effect( data.effect or "lvs_exhaust", effectdata )
end
end
function ENT:ExhaustEffectsThink()
if not self:IsBackFireEnabled() then return end
local Throttle = self:GetThrottle()
if self._backfireTHR ~= Throttle then
self._backfireTHR = Throttle
if Throttle ~= 0 then return end
self:CalcExhaustPop()
end
end
function ENT:CalcExhaustPop()
local Engine = self:GetEngine()
if not IsValid( Engine ) then return end
local RPM = Engine:GetRPM()
local num = (Engine:GetRPM() / 500)
local Throttle = self:GetThrottle()
if Throttle > 0 and Throttle < 0.6 then return end
if Throttle ~= 0 or (not IsValid( self:GetTurbo() ) and not IsValid( self:GetCompressor() )) then num = 0 end
for i = 0, num do
timer.Simple( self.TransShiftSpeed + i * 0.1 , function()
if not IsValid( self ) then return end
if i > 0 and self:GetThrottle() ~= 0 then return end
local Engine = self:GetEngine()
if not IsValid( Engine ) then return end
local RPM = Engine:GetRPM()
if RPM < self.EngineMaxRPM * 0.6 then return end
if i == 0 then
self:DoExhaustPop( LVS.EngineVolume )
else
self:DoExhaustPop( 0.75 * LVS.EngineVolume )
end
end )
end
end
function ENT:DoExhaustPop( volume )
if not istable( self.ExhaustPositions ) then return end
for _, data in ipairs( self.ExhaustPositions ) do
if data.bodygroup then
if not self:BodygroupIsValid( data.bodygroup.name, data.bodygroup.active ) then continue end
end
timer.Simple( math.Rand(0,0.2), function()
local effectdata = EffectData()
effectdata:SetOrigin( data.pos )
effectdata:SetAngles( data.ang )
effectdata:SetEntity( self )
effectdata:SetMagnitude( volume or 1 )
util.Effect( "lvs_carexhaust_pop", effectdata )
end )
end
end
function ENT:DoExhaustBackFire()
if not istable( self.ExhaustPositions ) then return end
for _, data in ipairs( self.ExhaustPositions ) do
if data.bodygroup then
if not self:BodygroupIsValid( data.bodygroup.name, data.bodygroup.active ) then continue end
end
if math.random( 1, math.floor( #self.ExhaustPositions * 0.75 ) ) ~= 1 then continue end
timer.Simple( math.Rand(0.5,1), function()
local effectdata = EffectData()
effectdata:SetOrigin( data.pos )
effectdata:SetAngles( data.ang )
effectdata:SetEntity( self )
util.Effect( "lvs_carexhaust_backfire", effectdata )
end )
end
end

View File

@@ -0,0 +1,72 @@
ENT.FlyByVelocity = 500
ENT.FlyByMinThrottle = 0
ENT.FlyByAdvance = 1
ENT.FlyBySound = "lvs/vehicles/generic/car_flyby.wav"
function ENT:FlyByThink()
local ply = LocalPlayer()
if not IsValid( ply ) then return end
local veh = ply:lvsGetVehicle()
local EntTable = self:GetTable()
if veh == self then EntTable.OldApproaching = false return end
local ViewEnt = ply:GetViewEntity()
if not IsValid( ViewEnt ) then return end
if IsValid( veh ) and ViewEnt == ply then
ViewEnt = veh
end
local Time = CurTime()
if (EntTable._nextflyby or 0) > Time then return end
EntTable._nextflyby = Time + 0.1
local Vel = self:GetVelocity()
if self:GetThrottle() <= EntTable.FlyByMinThrottle or Vel:Length() <= EntTable.FlyByVelocity then return end
local Sub = ViewEnt:GetPos() - self:GetPos() - Vel * EntTable.FlyByAdvance
local ToPlayer = Sub:GetNormalized()
local VelDir = Vel:GetNormalized()
local ApproachAngle = self:AngleBetweenNormal( ToPlayer, VelDir )
local Approaching = ApproachAngle < 80
if Approaching ~= EntTable.OldApproaching then
EntTable.OldApproaching = Approaching
if Approaching then
self:StopFlyBy()
else
self:OnFlyBy( 60 + 80 * math.min(ApproachAngle / 140,1) )
end
end
end
function ENT:OnFlyBy( Pitch )
if not self.FlyBySound then return end
local EntTable = self:GetTable()
EntTable.flybysnd = CreateSound( self, EntTable.FlyBySound )
EntTable.flybysnd:SetSoundLevel( 95 )
EntTable.flybysnd:PlayEx( 1, Pitch )
end
function ENT:StopFlyBy()
local EntTable = self:GetTable()
if not EntTable.flybysnd then return end
EntTable.flybysnd:Stop()
EntTable.flybysnd = nil
end

View File

@@ -0,0 +1,237 @@
include("cl_hud_speedometer.lua")
ENT.IconEngine = Material( "lvs/engine.png" )
ENT.IconFuel = Material( "lvs/fuel.png" )
local WaveScale = 0
local WaveMaterial = Material( "effects/select_ring" )
local oldThrottleActive = false
local oldReverse = false
local oldGear = -1
function ENT:LVSHudPaintInfoText( X, Y, W, H, ScrX, ScrY, ply )
if self:GetRacingHud() then return end
local EntTable = self:GetTable()
local T = CurTime()
if (EntTable._nextRefreshVel or 0) < T then
EntTable._nextRefreshVel = T + 0.1
EntTable._refreshVel = self:GetVelocity():Length()
end
local speed = math.Round( LVS:GetUnitValue( EntTable._refreshVel or 0 ) , 0 )
draw.DrawText( LVS:GetUnitName().." ", "LVS_FONT", X + 72, Y + 35, color_white, TEXT_ALIGN_RIGHT )
draw.DrawText( speed, "LVS_FONT_HUD_LARGE", X + 72, Y + 20, color_white, TEXT_ALIGN_LEFT )
if ply ~= self:GetDriver() then return end
local Throttle = self:GetThrottle()
local Col = Throttle <= 1 and color_white or Color(0,0,0,255)
local hX = X + W - H * 0.5
local hY = Y + H * 0.25 + H * 0.25
local fueltank = self:GetFuelTank()
if IsValid( fueltank ) and fueltank:GetFuel() <= 0 then
surface.SetMaterial( EntTable.IconFuel )
else
surface.SetMaterial( EntTable.IconEngine )
end
surface.SetDrawColor( 0, 0, 0, 200 )
surface.DrawTexturedRectRotated( hX + 4, hY + 1, H * 0.5, H * 0.5, 0 )
surface.SetDrawColor( color_white )
surface.DrawTexturedRectRotated( hX + 2, hY - 1, H * 0.5, H * 0.5, 0 )
if not self:GetEngineActive() then
draw.SimpleText( "X" , "LVS_FONT", hX, hY, Color(0,0,0,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER )
else
oldThrottleActive = false
local Reverse = self:GetReverse()
if oldReverse ~= Reverse then
oldReverse = Reverse
WaveScale = 1
end
local IsManual = self:IsManualTransmission()
local Gear = self:GetGear()
if oldGear ~= Gear then
oldGear = Gear
WaveScale = 1
end
if WaveScale > 0 then
WaveScale = math.max( WaveScale - RealFrameTime() * 2, 0 )
local WaveRadius = (1 - WaveScale) * H * 1.5
surface.SetDrawColor( 0, 127, 255, 255 * WaveScale ^ 2 )
surface.SetMaterial( WaveMaterial )
surface.DrawTexturedRectRotated( hX, hY, WaveRadius, WaveRadius, 0 )
if not Reverse and not IsManual then
draw.SimpleText( "D" , "LVS_FONT", hX, hY, Color(0,0,0,math.min(800 * WaveScale ^ 2,255)), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER )
end
end
if IsManual then
draw.SimpleText( (Reverse and -1 or 1) * Gear , "LVS_FONT", hX, hY, Color(0,0,0,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER )
else
if Reverse then
draw.SimpleText( "R" , "LVS_FONT", hX, hY, Color(0,0,0,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER )
end
end
end
self:LVSDrawCircle( hX, hY, H * 0.35, math.min( Throttle, 1 ) )
if Throttle > 1 then
draw.SimpleText( "+"..math.Round((Throttle - 1) * 100,0).."%" , "LVS_FONT", hX, hY, Col, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER )
end
end
LVS:AddHudEditor( "CarMenu", ScrW() - 690, ScrH() - 85, 220, 75, 220, 75, "CAR MENU",
function( self, vehicle, X, Y, W, H, ScrX, ScrY, ply )
if not vehicle.LVSHudPaintCarMenu then return end
vehicle:LVSHudPaintCarMenu( X, Y, W, H, ScrX, ScrY, ply )
end
)
local function DrawTexturedRect( X, Y, size, selected )
local oz = 2
surface.SetDrawColor( 0, 0, 0, 150 )
surface.DrawTexturedRectRotated( X + oz, Y + oz, size, size, 0 )
if IsColor( selected ) then
surface.SetDrawColor( selected.r, selected.g, selected.b, selected.a )
else
if selected then
surface.SetDrawColor( 255, 255, 255, 255 )
else
surface.SetDrawColor( 150, 150, 150, 150 )
end
end
surface.DrawTexturedRectRotated( X, Y, size, size, 0 )
end
ENT.CarMenuSiren = Material( "lvs/carmenu_siren.png" )
ENT.CarMenuHighbeam = Material( "lvs/carmenu_highbeam.png" )
ENT.CarMenuLowbeam = Material( "lvs/carmenu_lowbeam.png" )
ENT.CarMenuDisable = Material( "lvs/carmenu_cross.png" )
ENT.CarMenuFog = Material( "lvs/carmenu_fog.png" )
ENT.CarMenuHazard = Material( "lvs/carmenu_hazard.png" )
ENT.CarMenuLeft = Material( "lvs/carmenu_turnleft.png" )
ENT.CarMenuRight = Material( "lvs/carmenu_turnRight.png" )
function ENT:LVSHudPaintCarMenu( X, Y, w, h, ScrX, ScrY, ply )
if self:GetDriver() ~= ply then return end
local MenuOpen = ply:lvsKeyDown( "CAR_MENU" ) and self:HasTurnSignals()
local EntTable = self:GetTable()
if MenuOpen then
if ply:lvsKeyDown( "CAR_BRAKE" ) then
EntTable._SelectedMode = 3
end
if ply:lvsKeyDown( "CAR_THROTTLE" ) then
EntTable._SelectedMode = 0
end
if ply:lvsKeyDown( "CAR_STEER_LEFT" ) then
EntTable._SelectedMode = 1
end
if ply:lvsKeyDown( "CAR_STEER_RIGHT" ) then
EntTable._SelectedMode = 2
end
if EntTable._oldSelectedMode ~= EntTable._SelectedMode then
EntTable._oldSelectedMode = EntTable._SelectedMode
self:EmitSound("buttons/lightswitch2.wav",75,120,0.25)
end
else
if EntTable._oldSelectedMode and isnumber( EntTable._SelectedMode ) then
self:EmitSound("buttons/lightswitch2.wav",75,100,0.25)
net.Start( "lvs_car_turnsignal" )
net.WriteInt( EntTable._SelectedMode, 4 )
net.SendToServer()
EntTable._SelectedMode = 0
EntTable._oldSelectedMode = nil
end
local size = 32
local dist = 5
local cX = X + w * 0.5
local cY = Y + h - size * 0.5 - dist
local LightsHandler = self:GetLightsHandler()
if IsValid( LightsHandler ) then
if LightsHandler:GetActive() then
if LightsHandler:GetHighActive() then
surface.SetMaterial( self.CarMenuHighbeam )
DrawTexturedRect( cX, cY, size, Color(0,255,255,255) )
else
surface.SetMaterial( self.CarMenuLowbeam )
DrawTexturedRect( cX, cY, size, Color(0,255,0,255) )
end
end
if LightsHandler:GetFogActive() then
surface.SetMaterial( self.CarMenuFog )
DrawTexturedRect( cX, cY - (size + dist), size, Color(255,100,0,255) )
end
end
local TurnMode = self:GetTurnMode()
if TurnMode == 0 then return end
local Alpha = self:GetTurnFlasher() and 255 or 0
if TurnMode == 1 or TurnMode == 3 then
surface.SetMaterial( EntTable.CarMenuLeft )
DrawTexturedRect( cX - (size + dist), cY, size, Color(0,255,157,Alpha) )
end
if TurnMode == 2 or TurnMode == 3 then
surface.SetMaterial( EntTable.CarMenuRight )
DrawTexturedRect( cX + (size + dist), cY, size, Color(0,255,157,Alpha) )
end
return
end
local SelectedThing = EntTable._SelectedMode or 0
local size = 32
local dist = 5
local cX = X + w * 0.5
local cY = Y + h - size * 0.5 - dist
surface.SetMaterial( EntTable.CarMenuDisable )
DrawTexturedRect( cX, cY - (size + dist), size, SelectedThing == 0 )
surface.SetMaterial( EntTable.CarMenuLeft )
DrawTexturedRect( cX - (size + dist), cY, size, SelectedThing == 1 )
surface.SetMaterial( EntTable.CarMenuRight)
DrawTexturedRect( cX + (size + dist), cY, size, SelectedThing == 2 )
surface.SetMaterial( EntTable.CarMenuHazard )
DrawTexturedRect( cX, cY, size, SelectedThing == 3 )
end

View File

@@ -0,0 +1,403 @@
LVS:AddHudEditor( "Tachometer", ScrW() - 530, ScrH() - 250, 300, 220, 300, 220, "TACH",
function( self, vehicle, X, Y, W, H, ScrX, ScrY, ply )
if not vehicle.LVSHudPaintTach or not vehicle.GetRacingHud then return end
vehicle:LVSHudPaintTach( X, Y, W, H, ScrX, ScrY, ply )
end
)
local THE_FONT = {
font = "Verdana",
extended = false,
size = 100,
weight = 2000,
blursize = 0,
scanlines = 0,
antialias = false,
underline = false,
italic = false,
strikeout = false,
symbol = false,
rotary = false,
shadow = false,
additive = false,
outline = false,
}
surface.CreateFont( "LVS_TACHOMETER", THE_FONT )
local circles = include("includes/circles/circles.lua")
local Center = 650
local startAngleSpeedo = 180
local endAngleSpeedo = 375
local startAngleTach = 165
local endAngleTach = 360
local Ring = circles.New( CIRCLE_OUTLINED, 300, 0, 0, 60 )
Ring:SetMaterial( true )
local Circle = circles.New( CIRCLE_OUTLINED, 625, 0, 0, 230 )
Circle:SetX( Center )
Circle:SetY( Center )
Circle:SetMaterial( true )
local RingOuter = circles.New( CIRCLE_OUTLINED, 645, 0, 0, 35 )
RingOuter:SetX( Center )
RingOuter:SetY( Center )
RingOuter:SetMaterial( true )
local RingInner = circles.New( CIRCLE_OUTLINED, 640, 0, 0, 25 )
RingInner:SetX( Center )
RingInner:SetY( Center )
RingInner:SetMaterial( true )
local RingFrame = circles.New( CIRCLE_OUTLINED, 390, 0, 0, 10 )
RingFrame:SetX( Center )
RingFrame:SetY( Center )
RingFrame:SetMaterial( true )
local RingFrameOuter = circles.New( CIRCLE_OUTLINED, 395, 0, 0, 20 )
RingFrameOuter:SetX( Center )
RingFrameOuter:SetY( Center )
RingFrameOuter:SetMaterial( true )
local RingOuterRedline = circles.New( CIRCLE_OUTLINED, 645, 0, 0, 20 )
RingOuterRedline:SetX( Center )
RingOuterRedline:SetY( Center )
RingOuterRedline:SetMaterial( true )
local RingInnerRedline = circles.New( CIRCLE_OUTLINED, 640, 0, 0, 10 )
RingInnerRedline:SetX( Center )
RingInnerRedline:SetY( Center )
RingInnerRedline:SetMaterial( true )
local VehicleTach = {}
function ENT:GetBakedTachMaterial( MaxRPM )
local Class = self:GetClass()
if VehicleTach[ Class ] then return VehicleTach[ Class ] end
local TachRange = endAngleTach - startAngleTach
local Steps = math.ceil(MaxRPM / 1000)
local AngleStep = TachRange / Steps
local AngleRedline = startAngleTach + (TachRange / MaxRPM) * self.EngineMaxRPM
local tachRT = GetRenderTarget( "lvs_tach_"..Class, Center * 2, Center * 2 )
local old = DisableClipping( true )
render.OverrideAlphaWriteEnable( true, true )
render.PushRenderTarget( tachRT )
cam.Start2D()
render.ClearDepth()
render.Clear( 0, 0, 0, 0 )
surface.SetDrawColor( Color( 0, 0, 0, 150 ) )
Circle:SetStartAngle( startAngleTach )
Circle:SetEndAngle( endAngleTach )
Circle()
surface.SetDrawColor( Color( 0, 0, 0, 200 ) )
RingOuter:SetStartAngle( startAngleTach )
RingOuter:SetEndAngle( AngleRedline )
RingOuter()
RingOuterRedline:SetStartAngle( AngleRedline )
RingOuterRedline:SetEndAngle( endAngleTach )
RingOuterRedline()
RingFrameOuter:SetStartAngle( startAngleTach )
RingFrameOuter:SetEndAngle( endAngleTach )
RingFrameOuter()
surface.SetDrawColor( color_white )
for i = 0, Steps do
local Ang = AngleStep * i + startAngleTach
local AngX = math.cos( math.rad( Ang ) )
local AngY = math.sin( math.rad( Ang ) )
local StartX = Center + AngX * 554
local StartY = Center + AngY * 554
local EndX = Center + AngX * 635
local EndY = Center + AngY * 635
if Ang > AngleRedline then
surface.SetDrawColor( Color(255,0,0,255) )
else
surface.SetDrawColor( color_white )
end
draw.NoTexture()
surface.DrawTexturedRectRotated( (StartX + EndX) * 0.5, (StartY + EndY) * 0.5, 90, 15, -Ang )
local TextX = Center + AngX * 485
local TextY = Center + AngY * 485
if Ang > AngleRedline then
draw.SimpleText( i, "LVS_TACHOMETER", TextX, TextY, Color(255,0,0,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER )
else
draw.SimpleText( i, "LVS_TACHOMETER", TextX, TextY, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER )
end
end
for i = 1, Steps do
local Start = AngleStep * i + startAngleTach
for n = 1, 9 do
local Ang = Start - (AngleStep / 10) * n
if Ang > AngleRedline then
surface.SetDrawColor( Color(150,0,0,255) )
else
surface.SetDrawColor( Color(150,150,150,255) )
end
local AngX = math.cos( math.rad( Ang ) )
local AngY = math.sin( math.rad( Ang ) )
local StartX = Center + AngX * 575
local StartY = Center + AngY * 575
local EndX = Center + AngX * 635
local EndY = Center + AngY * 635
draw.NoTexture()
surface.DrawTexturedRectRotated( (StartX + EndX) * 0.5, (StartY + EndY) * 0.5, 60, 5, -Ang )
end
end
surface.SetDrawColor( color_white )
RingInner:SetStartAngle( startAngleTach )
RingInner:SetEndAngle( AngleRedline )
RingInner()
RingFrame:SetStartAngle( startAngleTach )
RingFrame:SetEndAngle( endAngleTach )
RingFrame()
surface.SetDrawColor( Color(255,0,0,255) )
RingInnerRedline:SetStartAngle( AngleRedline )
RingInnerRedline:SetEndAngle( endAngleTach )
RingInnerRedline()
cam.End2D()
render.OverrideAlphaWriteEnable( false )
render.PopRenderTarget()
local Mat = CreateMaterial( "lvs_tach_"..Class.."_mat", "UnlitGeneric", { ["$basetexture"] = tachRT:GetName(), ["$translucent"] = 1, ["$vertexcolor"] = 1 } )
VehicleTach[ Class ] = Mat
DisableClipping( old )
return Mat
end
local TachNeedleColor = Color(255,0,0,255)
local TachNeedleRadiusInner = 90
local TachNeedleRadiusOuter = 145
local TachNeedleBlurTime = 0.1
local TachNeedles = {}
local CurRPM = 0
local CurSpeed = 0
function ENT:LVSHudPaintTach( X, Y, w, h, ScrX, ScrY, ply )
if ply ~= self:GetDriver() then return end
if not self:GetRacingHud() then return end
local Engine = self:GetEngine()
if not IsValid( Engine ) then return end
local Delta = (Engine:GetRPM() - CurRPM) * RealFrameTime() * 20
CurRPM = CurRPM + Delta
local EntTable = self:GetTable()
local MaxRPM = EntTable.EngineMaxRPM + 3000
local Ang = startAngleTach + (endAngleTach - startAngleTach) * (CurRPM / MaxRPM)
local T = CurTime()
local FuelTank = self:GetFuelTank()
local UsesFuel = IsValid( FuelTank )
if self:GetEngineActive() then
local Gear = self:GetGear()
local printGear = Gear
if Gear == -1 then
printGear = self:GetReverse() and "R" or "D"
else
if self:GetReverse() then
printGear = "-"..Gear
end
end
draw.DrawText( printGear, "LVS_FONT_HUD_HUMONGOUS", X + w * 0.5, Y + w * 0.23, color_white, TEXT_ALIGN_CENTER )
else
surface.SetMaterial( EntTable.IconEngine )
if UsesFuel and FuelTank:GetFuel() <= 0 then
surface.SetMaterial( EntTable.IconFuel )
end
surface.SetDrawColor( Color(255,0,0, math.abs( math.cos( T * 5 ) ) * 255 ) )
surface.DrawTexturedRectRotated( X + w * 0.5 + 2, Y + w * 0.35 - 1, w * 0.15, w * 0.15, 0 )
end
if (EntTable._nextRefreshVel or 0) < T then
EntTable._nextRefreshVel = T + 0.1
EntTable._refreshVel = self:GetVelocity():Length()
end
local speed = math.Round( LVS:GetUnitValue( EntTable._refreshVel or 0 ) , 0 )
draw.DrawText( LVS:GetUnitName().." ", "LVS_FONT", X + w * 0.81, Y + w * 0.6, color_white, TEXT_ALIGN_LEFT )
draw.DrawText( speed, "LVS_FONT_HUD_LARGE", X + w * 0.81 - 5, Y + w * 0.6, color_white, TEXT_ALIGN_RIGHT )
-- fuel, oil, coolant
local barlength = w * 0.2
if UsesFuel then
surface.SetDrawColor( 0, 0, 0, 200 )
surface.DrawRect( X + w * 0.5 - barlength * 0.5 - 1, Y + w * 0.5 - 1, barlength + 2, 7 )
local col = LVS.FUELTYPES[ FuelTank:GetFuelType() ].color
surface.SetDrawColor( Color(col.r,col.g,col.b,255) )
surface.DrawRect( X + w * 0.5 - barlength * 0.5, Y + w * 0.5, barlength * FuelTank:GetFuel(), 5 )
draw.DrawText( "fuel", "LVS_FONT_PANEL", X + w * 0.5 + barlength * 0.5 + 5, Y + w * 0.5 - 5, Color(255,150,0,255), TEXT_ALIGN_LEFT )
end
if self:HasQuickVar( "oil" ) then
surface.SetDrawColor( 0, 0, 0, 200 )
surface.DrawRect( X + w * 0.5 - barlength * 0.5 - 1, Y + w * 0.5 - 1 + 10, barlength + 2, 7 )
surface.SetDrawColor( 80, 80, 80, 255 )
surface.DrawRect( X + w * 0.5 - barlength * 0.5, Y + w * 0.5 + 10, barlength * math.min(self:GetQuickVar( "oil" ),1), 5 )
draw.DrawText( "oil pressure", "LVS_FONT_PANEL", X + w * 0.5 + barlength * 0.5 + 5, Y + w * 0.5 + 5, Color(0, 0, 0, 255), TEXT_ALIGN_LEFT )
end
if self:HasQuickVar( "temp" ) then
surface.SetDrawColor( 0, 0, 0, 200 )
surface.DrawRect( X + w * 0.5 - barlength * 0.5 - 1, Y + w * 0.5 - 1 + 20, barlength + 2, 7 )
surface.SetDrawColor( 0, 127, 255, 255 )
surface.DrawRect( X + w * 0.5 - barlength * 0.5, Y + w * 0.5 + 20, barlength * math.min(self:GetQuickVar( "temp" ),1), 5 )
draw.DrawText( "coolant temp", "LVS_FONT_PANEL", X + w * 0.5 + barlength * 0.5 + 5, Y + w * 0.5 + 15, Color(0, 0, 255, 255), TEXT_ALIGN_LEFT )
end
-- brake, clutch, throttle bar
local throttle = self:GetThrottle()
local clutch = self:GetQuickVar( "clutch" )
local hasClutch = self:HasQuickVar( "clutch" )
local brake = self:GetBrake()
local engine = self:GetEngine()
if IsValid( engine ) then
local ClutchActive = engine:GetClutch()
if not clutch then
clutch = ClutchActive and 1 or 0
end
if ClutchActive then
throttle = math.max( throttle - clutch, 0 )
end
end
surface.SetDrawColor( 0, 0, 0, 200 )
surface.DrawRect( X + w * 0.3 - 1, Y + w * 0.4 - 1, 7, barlength + 2 )
surface.DrawRect( X + w * 0.3 + 10 - 1, Y + w * 0.4 - 1, 7, barlength + 2 )
if hasClutch then surface.DrawRect( X + w * 0.3 - 10 - 1, Y + w * 0.4 - 1, 7, barlength + 2 ) end
surface.SetDrawColor( 255, 255, 255, 255 )
if hasClutch then
local cllength = barlength * clutch
surface.DrawRect( X + w * 0.3 - 10, Y + w * 0.4 + barlength - cllength, 5, cllength )
end
local brlength = barlength * brake
surface.DrawRect( X + w * 0.3, Y + w * 0.4 + barlength - brlength, 5, brlength )
local thrlength = barlength * throttle
surface.DrawRect( X + w * 0.3 + 10, Y + w * 0.4 + barlength - thrlength, 5, thrlength )
if hasClutch then draw.DrawText( "c", "LVS_FONT_PANEL", X + w * 0.3 - 7, Y + w * 0.4 + barlength, color_white, TEXT_ALIGN_CENTER ) end
draw.DrawText( "b", "LVS_FONT_PANEL", X + w * 0.3 + 3, Y + w * 0.4 + barlength, color_white, TEXT_ALIGN_CENTER )
draw.DrawText( "t", "LVS_FONT_PANEL", X + w * 0.3 + 13, Y + w * 0.4 + barlength, color_white, TEXT_ALIGN_CENTER )
local TachRange = endAngleTach - startAngleTach
local AngleRedline = startAngleTach + (TachRange / MaxRPM) * EntTable.EngineMaxRPM
Ring:SetX( X + w * 0.5 )
Ring:SetY( Y + w * 0.5 )
Ring:SetRadius( w * 0.49 )
Ring:SetOutlineWidth( w * 0.04 )
Ring:SetStartAngle( startAngleTach )
Ring:SetEndAngle( math.min( Ang, AngleRedline ) )
Ring()
if Ang > AngleRedline then
surface.SetDrawColor( 255, 0, 0, 255 )
Ring:SetStartAngle( AngleRedline )
Ring:SetEndAngle( math.min( Ang, endAngleTach ) )
Ring()
end
surface.SetDrawColor( 255, 255, 255, 255 )
surface.SetMaterial( self:GetBakedTachMaterial( MaxRPM ) )
surface.DrawTexturedRect( X, Y, w, w )
local CenterX = X + w * 0.5
local CenterY = Y + w * 0.5
local T = CurTime()
local AngX = math.cos( math.rad( Ang ) )
local AngY = math.sin( math.rad( Ang ) )
if math.abs( Delta ) > 1 then
local data = {
StartX = (CenterX + AngX * TachNeedleRadiusInner),
StartY = (CenterY + AngY * TachNeedleRadiusInner),
EndX = (CenterX + AngX * TachNeedleRadiusOuter),
EndY = (CenterY + AngY * TachNeedleRadiusOuter),
Time = T + TachNeedleBlurTime
}
table.insert( TachNeedles, data )
else
local StartX = CenterX + AngX * TachNeedleRadiusInner
local StartY = CenterY + AngY * TachNeedleRadiusInner
local EndX = CenterX + AngX * TachNeedleRadiusOuter
local EndY = CenterY + AngY * TachNeedleRadiusOuter
surface.SetDrawColor( TachNeedleColor )
surface.DrawLine( StartX, StartY, EndX, EndY )
end
for index, data in pairs( TachNeedles ) do
if data.Time < T then
TachNeedles[ index ] = nil
continue
end
local Brightness = (data.Time - T) / TachNeedleBlurTime
surface.SetDrawColor( Color( TachNeedleColor.r * Brightness, TachNeedleColor.g * Brightness, TachNeedleColor.b * Brightness, TachNeedleColor.a * Brightness ^ 2 ) )
surface.DrawLine( data.StartX, data.StartY, data.EndX, data.EndY )
end
end

View File

@@ -0,0 +1,192 @@
include("shared.lua")
include("sh_animations.lua")
include("sh_camera_eyetrace.lua")
include("cl_flyby.lua")
include("cl_tiresounds.lua")
include("cl_camera.lua")
include("cl_hud.lua")
include("cl_scrolltexture.lua")
include("cl_exhausteffects.lua")
DEFINE_BASECLASS( "lvs_base" )
function ENT:CreateSubMaterial( SubMaterialID, name )
if not SubMaterialID then return end
local mat = self:GetMaterials()[ SubMaterialID + 1 ]
if not mat then return end
local string_data = file.Read( "materials/"..mat..".vmt", "GAME" )
if not string_data then return end
return CreateMaterial( name, "VertexLitGeneric", util.KeyValuesToTable( string_data ) )
end
function ENT:CalcPoseParameters()
local steer = self:GetSteer() / self:GetMaxSteerAngle()
local kmh = math.Round( self:GetVelocity():Length() * 0.09144, 0 )
local lights = self:GetLightsHandler()
local ammeter = 0.5
local rpm = 0
local oil = 0.25
local gear = 1
local clutch = 0
local throttle = self:GetThrottle()
local engine = self:GetEngine()
local engineActive = self:GetEngineActive()
local fuel = 1
local fueltank = self:GetFuelTank()
local handbrake = self:QuickLerp( "handbrake", self:GetNWHandBrake() and 1 or 0 )
local temperature = 0
if IsValid( engine ) then
local EngineHealthFraction = engine:GetHP() / engine:GetMaxHP()
rpm = self:QuickLerp( "rpm", engine:GetRPM() )
gear = engine:GetGear()
oil = self:QuickLerp( "oil", engineActive and math.min( (EngineHealthFraction ^ 2) * 0.2 + (rpm / self.EngineMaxRPM) * 1.25 - (math.max( rpm - self.EngineMaxRPM, 0 ) / 2000) * 1.6, 1 ) or 0, 2 ) ^ 2
local ClutchActive = engine:GetClutch()
clutch = self:QuickLerp( "clutch", ClutchActive and 1 or 0 )
if ClutchActive then
throttle = math.max( throttle - clutch, 0 )
end
temperature = self:QuickLerp( "temp", self:QuickLerp( "base_temp", engineActive and 0.5 or 0, 0.025 + throttle * 0.1 ) + (1 - EngineHealthFraction) ^ 2 * 1.25, 0.5 )
else
temperature = self:QuickLerp( "temp", self:QuickLerp( "base_temp", engineActive and 0.5 or 0, 0.025 + throttle * 0.1 ) + (1 - self:GetHP() / self:GetMaxHP()) ^ 2 * 1.25, 0.5 )
end
if IsValid( lights ) then
local Available = 0.5 + (rpm / self.EngineMaxRPM) * 0.25
local Use1 = lights:GetActive() and 0.1 or 0
local Use2 = lights:GetHighActive() and 0.15 or 0
local Use3 = lights:GetFogActive() and 0.05 or 0
local Use4 = (self:GetTurnMode() ~= 0 and self:GetTurnFlasher()) and 0.03 or 0
ammeter = self:QuickLerp( "ammeter", math.max( Available - Use1 - Use2 - Use3 - Use4, 0 ), math.Rand(1,10) )
end
if IsValid( fueltank ) then
fuel = self:QuickLerp( "fuel", fueltank:GetFuel() )
end
self:UpdatePoseParameters( steer, self:QuickLerp( "kmh", kmh ), rpm, throttle, self:GetBrake(), handbrake, clutch, (self:GetReverse() and -gear or gear), temperature, fuel, oil, ammeter )
self:InvalidateBoneCache()
end
function ENT:Think()
if not self:IsInitialized() then return end
BaseClass.Think( self )
self:TireSoundThink()
self:ExhaustEffectsThink()
if isfunction( self.UpdatePoseParameters ) then
self:CalcPoseParameters()
else
self:SetPoseParameter( "vehicle_steer", self:GetSteer() / self:GetMaxSteerAngle() )
self:InvalidateBoneCache()
end
end
function ENT:OnRemove()
self:TireSoundRemove()
BaseClass.OnRemove( self )
end
function ENT:PostDrawTranslucent()
local Handler = self:GetLightsHandler()
if not IsValid( Handler ) or not istable( self.Lights ) then return end
Handler:RenderLights( self, self.Lights )
end
function ENT:OnEngineStallBroken()
for i = 0,math.random(3,6) do
timer.Simple( math.Rand(0,1.5) , function()
if not IsValid( self ) then return end
self:DoExhaustBackFire()
end )
end
end
function ENT:OnChangeGear( oldGear, newGear )
local HP = self:GetHP()
local MaxHP = self:GetMaxHP()
local Engine = self:GetEngine()
local EngineHP = 0
local EngineMaxHP = 0
if IsValid( Engine ) then
EngineHP = Engine:GetHP()
EngineMaxHP = Engine:GetMaxHP()
end
local Damaged = HP < MaxHP * 0.5
local EngineDamaged = EngineHP < EngineMaxHP * 0.5
if (Damaged or EngineDamaged) then
if oldGear > newGear then
if Damaged then
self:EmitSound( "lvs/vehicles/generic/gear_grind"..math.random(1,6)..".ogg", 75, math.Rand(70,100), 0.25 )
self:DoExhaustBackFire()
end
else
if EngineDamaged then
self:DoExhaustBackFire()
end
end
else
self:EmitSound( self.TransShiftSound, 75 )
if self:IsBackFireEnabled() then
self:CalcExhaustPop()
end
end
self:SuppressViewPunch( self.TransShiftSpeed )
end
function ENT:GetTurnFlasher()
return math.cos( CurTime() * 8 + self:EntIndex() * 1337 ) > 0
end
function ENT:OnEngineActiveChanged( Active )
if Active then
self:EmitSound( "lvs/vehicles/generic/engine_start1.wav", 75, 100, LVS.EngineVolume )
else
self:EmitSound( "vehicles/jetski/jetski_off.wav", 75, 100, LVS.EngineVolume )
end
end
function ENT:GetWheels()
local wheels = {}
for _, ent in pairs( self:GetCrosshairFilterEnts() ) do
if not IsValid( ent ) or ent:GetClass() ~= "lvs_wheeldrive_wheel" then continue end
table.insert( wheels, ent )
end
return wheels
end

View File

@@ -0,0 +1,83 @@
ENT.ScrollTextureData = {
["$alphatest"] = "1",
["$translate"] = "[0.0 0.0 0.0]",
["$colorfix"] = "{255 255 255}",
["Proxies"] = {
["TextureTransform"] = {
["translateVar"] = "$translate",
["centerVar"] = "$center",
["resultVar"] = "$basetexturetransform",
},
["Equals"] = {
["srcVar1"] = "$colorfix",
["resultVar"] = "$color",
}
}
}
function ENT:GetRotationDelta( name, rot )
name = "_deltaAng"..name
if not self[ name ] then self[ name ] = Angle(0,0,0) end
local ang = Angle(0,0,rot)
local cur = ang:Right()
local old = self[ name ]:Up()
local delta = self:AngleBetweenNormal( cur, old ) - 90
self[ name ] = ang
return delta
end
function ENT:CalcScroll( name, rot )
local delta = self:GetRotationDelta( name, rot )
name = "_deltaScroll"..name
if not self[ name ] then self[ name ] = 0 end
self[ name ] = self[ name ] + delta
if self[ name ] > 32768 then
self[ name ] = self[ name ] - 32768
end
if self[ name ] < -32768 then
self[ name ] = self[ name ] + 32768
end
return self[ name ]
end
function ENT:ScrollTexture( name, material, pos )
if not isstring( name ) or not isstring( material ) or not isvector( pos ) then return "" end
local id = self:EntIndex()
local class = self:GetClass()
local EntTable = self:GetTable()
local texture_name = class.."_["..id.."]_"..name
if istable( EntTable._StoredScrollTextures ) then
if EntTable._StoredScrollTextures[ texture_name ] then
self._StoredScrollTextures[ texture_name ]:SetVector("$translate", pos )
return "!"..texture_name
end
else
EntTable._StoredScrollTextures = {}
end
local data = table.Copy( EntTable.ScrollTextureData )
data["$basetexture"] = material
local mat = CreateMaterial(texture_name, "VertexLitGeneric", data )
EntTable._StoredScrollTextures[ texture_name ] = mat
return "!"..texture_name
end

View File

@@ -0,0 +1,84 @@
ENT.TireSoundFade = 0.15
ENT.TireSoundTypes = {
["roll"] = "lvs/vehicles/generic/wheel_roll.wav",
["roll_racing"] = "lvs/vehicles/generic/wheel_roll.wav",
["roll_dirt"] = "lvs/vehicles/generic/wheel_roll_dirt.wav",
["roll_wet"] = "lvs/vehicles/generic/wheel_roll_wet.wav",
["roll_damaged"] = "lvs/wheel_damaged_loop.wav",
["skid"] = "lvs/vehicles/generic/wheel_skid.wav",
["skid_racing"] = "lvs/vehicles/generic/wheel_skid_racing.wav",
["skid_dirt"] = "lvs/vehicles/generic/wheel_skid_dirt.wav",
["skid_wet"] = "lvs/vehicles/generic/wheel_skid_wet.wav",
["tire_damage_layer"] = "lvs/wheel_destroyed_loop.wav",
}
ENT.TireSoundLevelSkid = 85
ENT.TireSoundLevelRoll = 75
function ENT:TireSoundRemove()
for snd, _ in pairs( self.TireSoundTypes ) do
self:StopTireSound( snd )
end
end
function ENT:TireSoundThink()
for snd, _ in pairs( self.TireSoundTypes ) do
local T = self:GetTireSoundTime( snd )
if T > 0 then
local speed = self:GetVelocity():Length()
local sound = self:StartTireSound( snd )
if string.StartsWith( snd, "skid" ) or snd == "tire_damage_layer" then
local vel = speed
speed = math.max( math.abs( self:GetWheelVelocity() ) - vel, 0 ) * 5 + vel
end
local volume = math.min(speed / 1000,1) ^ 2 * T
local pitch = 100 + math.Clamp((speed - 400) / 200,0,155)
sound:ChangeVolume( volume, 0 )
sound:ChangePitch( pitch, 0.5 )
else
self:StopTireSound( snd )
end
end
end
function ENT:DoTireSound( snd )
if not istable( self._TireSounds ) then
self._TireSounds = {}
end
self._TireSounds[ snd ] = CurTime() + self.TireSoundFade
end
function ENT:GetTireSoundTime( snd )
if not istable( self._TireSounds ) or not self._TireSounds[ snd ] then return 0 end
return math.max(self._TireSounds[ snd ] - CurTime(),0) / self.TireSoundFade
end
function ENT:StartTireSound( snd )
if not self.TireSoundTypes[ snd ] or not istable( self._ActiveTireSounds ) then
self._ActiveTireSounds = {}
end
if self._ActiveTireSounds[ snd ] then return self._ActiveTireSounds[ snd ] end
local sound = CreateSound( self, self.TireSoundTypes[ snd ] )
sound:SetSoundLevel( string.StartsWith( snd, "skid" ) and self.TireSoundLevelSkid or self.TireSoundLevelRoll )
sound:PlayEx(0,100)
self._ActiveTireSounds[ snd ] = sound
return sound
end
function ENT:StopTireSound( snd )
if not istable( self._ActiveTireSounds ) or not self._ActiveTireSounds[ snd ] then return end
self._ActiveTireSounds[ snd ]:Stop()
self._ActiveTireSounds[ snd ] = nil
end

View File

@@ -0,0 +1,534 @@
AddCSLuaFile( "shared.lua" )
AddCSLuaFile( "cl_init.lua" )
AddCSLuaFile( "cl_flyby.lua" )
AddCSLuaFile( "cl_camera.lua" )
AddCSLuaFile( "cl_hud.lua" )
AddCSLuaFile( "cl_hud_speedometer.lua" )
AddCSLuaFile( "cl_tiresounds.lua" )
AddCSLuaFile( "cl_scrolltexture.lua" )
AddCSLuaFile( "cl_exhausteffects.lua" )
AddCSLuaFile( "sh_animations.lua" )
AddCSLuaFile( "sh_camera_eyetrace.lua" )
include("shared.lua")
include("sh_animations.lua")
include("sv_controls.lua")
include("sv_controls_handbrake.lua")
include("sv_components.lua")
include("sv_ai.lua")
include("sv_riggedwheels.lua")
include("sv_wheelsystem.lua")
include("sv_damage.lua")
include("sv_pivotsteer.lua")
include("sv_manualtransmission.lua")
include("sv_engine.lua")
include("sv_hydraulics.lua")
include("sh_camera_eyetrace.lua")
ENT.DriverActiveSound = "common/null.wav"
ENT.DriverInActiveSound = "common/null.wav"
DEFINE_BASECLASS( "lvs_base" )
local function SetMinimumAngularVelocityTo( new )
local tbl = physenv.GetPerformanceSettings()
if tbl.MaxAngularVelocity < new then
local OldAngVel = tbl.MaxAngularVelocity
tbl.MaxAngularVelocity = new
physenv.SetPerformanceSettings( tbl )
print("[LVS-Cars] Wheels require higher MaxAngularVelocity to perform correctly! Increasing! "..OldAngVel.." =>"..new)
end
end
local function IsServerOK( class )
if GetConVar( "gmod_physiterations" ):GetInt() ~= 4 then
RunConsoleCommand("gmod_physiterations", "4")
return false
end
return true
end
function ENT:TracksCreate( PObj )
end
local function DontDuplicatePaintSheme( ply, ent, data )
ent.RandomColor = nil
if not duplicator or not duplicator.StoreEntityModifier then return end
duplicator.StoreEntityModifier( ent, "lvsVehiclePaintSheme", data )
end
if duplicator and duplicator.RegisterEntityModifier then
duplicator.RegisterEntityModifier( "lvsVehiclePaintSheme", DontDuplicatePaintSheme )
end
function ENT:PostInitialize( PObj )
self:TracksCreate( PObj )
if not IsServerOK( self:GetClass() ) then
self:Remove()
print("[LVS] ERROR COULDN'T INITIALIZE VEHICLE!")
end
if istable( self.Lights ) then
self:AddLights()
end
if istable( self.RandomColor ) then
local data = self.RandomColor[ math.random( #self.RandomColor ) ]
if IsColor( data ) then
self:SetColor( data )
else
self:SetSkin( data.Skin or 0 )
self:SetColor( data.Color or color_white )
if istable( data.Wheels ) then
self._WheelSkin = data.Wheels.Skin or 0
self._WheelColor = data.Wheels.Color or color_white
end
if istable( data.BodyGroups ) then
for id, subgroup in pairs( data.BodyGroups ) do
self:SetBodygroup( id, subgroup )
end
end
end
DontDuplicatePaintSheme( NULL, self, {} )
end
BaseClass.PostInitialize( self, PObj )
if isstring( self.HornSound ) and isvector( self.HornPos ) and #self.WEAPONS[1] == 0 then
if IsValid( self.HornSND ) then self.HornSND:Remove() end
self.HornSND = self:AddSoundEmitter( self.HornPos or vector_origin, self.HornSound, self.HornSoundInterior )
self.HornSND:SetSoundLevel( 75 )
self.HornSND:SetDoppler( true )
end
if istable( self.SirenSound ) then
if IsValid( self.SirenSND ) then self.SirenSND:Remove() end
self.SirenSND = self:AddSoundEmitter( self.SirenPos or vector_origin, "common/null.wav" )
self.SirenSND:SetSoundLevel( 75 )
self.SirenSND:SetDoppler( true )
end
PObj:SetMass( self.PhysicsMass * self.PhysicsWeightScale )
PObj:EnableDrag( false )
PObj:SetInertia( self.PhysicsInertia * self.PhysicsWeightScale )
SetMinimumAngularVelocityTo( 24000 )
if BRANCH == "dev" or BRANCH == "x86-64" then
for _, wheel in pairs( self:GetWheels() ) do
if not IsValid( wheel ) then continue end
wheel:SetLightingOriginEntity( self )
end
end
self:EnableHandbrake()
end
function ENT:AlignView( ply )
if not IsValid( ply ) then return end
timer.Simple( 0, function()
if not IsValid( ply ) or not IsValid( self ) then return end
local Ang = Angle(0,90,0)
local pod = ply:GetVehicle()
local MouseAim = ply:lvsMouseAim() and self:GetDriver() == ply
if MouseAim and IsValid( pod ) then
Ang = pod:LocalToWorldAngles( Angle(0,90,0) )
Ang.r = 0
end
ply:SetEyeAngles( Ang )
end)
end
function ENT:PhysicsSimulateOverride( ForceAngle, phys, deltatime, simulate )
if not self:GetRacingTires() then
return ForceAngle, vector_origin, simulate
end
local EntTable = self:GetTable()
local WheelSideForce = EntTable.WheelSideForce * EntTable.ForceLinearMultiplierRacingTires
local ForceLinear = Vector(0,0,0)
for id, wheel in pairs( self:GetWheels() ) do
if wheel:IsHandbrakeActive() or not wheel:PhysicsOnGround() then continue end
local AxleAng = wheel:GetDirectionAngle()
local Forward = AxleAng:Forward()
local Right = AxleAng:Right()
local Up = AxleAng:Up()
local wheelPos = wheel:GetPos()
local wheelVel = phys:GetVelocityAtPoint( wheelPos )
local wheelRadius = wheel:GetRadius()
local Slip = math.Clamp(1 - self:AngleBetweenNormal( Forward, wheelVel:GetNormalized() ) / 90,0,1)
local ForwardVel = self:VectorSplitNormal( Forward, wheelVel )
Force = -Right * self:VectorSplitNormal( Right, wheelVel ) * WheelSideForce * Slip
local wSideForce, wAngSideForce = phys:CalculateVelocityOffset( Force, wheelPos )
ForceAngle:Add( Vector(0,0,wAngSideForce.z) )
ForceLinear:Add( wSideForce )
end
return ForceAngle, ForceLinear, simulate
end
function ENT:PhysicsSimulate( phys, deltatime )
if self:GetEngineActive() then phys:Wake() end
local ent = phys:GetEntity()
if ent == self then
local Vel = 0
for _, wheel in pairs( self:GetWheels() ) do
if wheel:GetTorqueFactor() <= 0 then continue end
local wheelVel = wheel:RPMToVel( math.abs( wheel:GetRPM() or 0 ) )
if wheelVel > Vel then
Vel = wheelVel
end
end
self:SetWheelVelocity( Vel )
if not self:StabilityAssist() or not self:WheelsOnGround() then return self:PhysicsSimulateOverride( Vector(0,0,0), phys, deltatime, SIM_NOTHING ) end
local ForceAngle = Vector(0,0, math.deg( -phys:GetAngleVelocity().z ) * math.min( phys:GetVelocity():Length() / self.PhysicsDampingSpeed, 1 ) * self.ForceAngleMultiplier )
return self:PhysicsSimulateOverride( ForceAngle, phys, deltatime, SIM_GLOBAL_ACCELERATION )
end
if not self:AlignWheel( ent ) or self:IsDestroyed() then self:EnableHandbrake() return vector_origin, vector_origin, SIM_NOTHING end
local WheelTable = ent:GetTable()
local EntTable = self:GetTable()
if ent:IsHandbrakeActive() then
if WheelTable.SetRPM then
ent:SetRPM( 0 )
end
return vector_origin, vector_origin, SIM_NOTHING
end
local T = CurTime()
if (WheelTable._NextSimulate or 0) < T or not WheelTable.Simulate then
WheelTable._NextSimulate = T + ((self:PivotSteer() or self:GetBrake() > 0) and EntTable.WheelTickIntervalBraking or EntTable.WheelTickInterval)
WheelTable.Force, WheelTable.ForceAng, WheelTable.Simulate = self:SimulateRotatingWheel( ent, EntTable, WheelTable, phys, deltatime )
end
return WheelTable.Force, WheelTable.ForceAng, WheelTable.Simulate
end
function ENT:SimulateRotatingWheel( ent, EntTable, WheelTable, phys, deltatime )
local RotationAxis = ent:GetRotationAxis()
local curRPM = self:VectorSplitNormal( RotationAxis, phys:GetAngleVelocity() ) / 6
local Throttle = self:GetThrottle()
ent:SetRPM( curRPM )
local ForceAngle = vector_origin
local ForceLinear = Vector(0,0,0)
local TorqueFactor = ent:GetTorqueFactor()
local IsBraking = self:GetBrake() > 0
local IsBrakingWheel = (TorqueFactor * Throttle) <= 0.99
if IsBraking and IsBrakingWheel then
if ent:IsRotationLocked() then
ForceAngle = vector_origin
else
local ForwardVel = self:VectorSplitNormal( ent:GetDirectionAngle():Forward(), phys:GetVelocity() )
local targetRPM = ent:VelToRPM( ForwardVel ) * 0.5
if math.abs( curRPM ) < EntTable.WheelBrakeLockupRPM then
ent:LockRotation()
else
if (ForwardVel > 0 and targetRPM > 0) or (ForwardVel < 0 and targetRPM < 0) then
ForceAngle = RotationAxis * math.Clamp( (targetRPM - curRPM) / 100,-1,1) * math.deg( EntTable.WheelBrakeForce ) * ent:GetBrakeFactor() * self:GetBrake()
end
end
end
else
if math.abs( curRPM ) < EntTable.WheelBrakeLockupRPM and Throttle == 0 then
ent:LockRotation()
else
if ent:IsRotationLocked() then
ent:ReleaseRotation()
end
end
if TorqueFactor > 0 and Throttle > 0 then
local engineTorque = self:GetEngineTorque()
local targetVelocity = self:GetTargetVelocity()
local targetRPM = ent:VelToRPM( targetVelocity )
local targetRPMabs = math.abs( targetRPM )
local powerRPM = targetRPMabs * EntTable.EngineCurve
local powerCurve = (powerRPM + math.max( targetRPMabs - powerRPM,0) - math.max(math.abs(curRPM) - powerRPM,0)) / targetRPMabs * self:Sign( targetRPM - curRPM )
local Torque = powerCurve * engineTorque * TorqueFactor * Throttle
local BoostRPM = 0
if self:GetReverse() then
Torque = math.min( Torque, 0 )
BoostRPM = ent:VelToRPM( EntTable.MaxVelocityReverse / EntTable.TransGearsReverse ) * 0.5
else
Torque = math.max( Torque, 0 )
BoostRPM = ent:VelToRPM( EntTable.MaxVelocity / EntTable.TransGears ) * 0.5
end
local BoostMul = math.max( EntTable.EngineCurveBoostLow, 0 )
local BoostStart = 1 + BoostMul
local TorqueBoost = BoostStart - (math.min( math.max( math.abs( curRPM ) - BoostRPM, 0 ), BoostRPM) / BoostRPM) * BoostMul
local Forward = ent:GetDirectionAngle():Forward()
local curVelocity = self:VectorSplitNormal( Forward, phys:GetVelocity() )
if targetVelocity >= 0 then
if curVelocity < targetVelocity then
ForceAngle = RotationAxis * Torque * TorqueBoost
end
else
if curVelocity > targetVelocity then
ForceAngle = RotationAxis * Torque * TorqueBoost
end
end
if self:PivotSteer() then
local RotationDirection = ent:GetWheelType() * self:GetPivotSteer()
if EntTable.PivotSteerByBrake and RotationDirection < 0 then
ent:LockRotation( true )
return vector_origin, vector_origin, SIM_NOTHING
end
powerCurve = math.Clamp((EntTable.PivotSteerWheelRPM * RotationDirection - curRPM) / EntTable.PivotSteerWheelRPM,-1,1)
Torque = powerCurve * engineTorque * TorqueFactor * Throttle * 2 * EntTable.PivotSteerTorqueMul
ForceAngle = RotationAxis * Torque
end
end
end
if not self:StabilityAssist() or not self:WheelsOnGround() then return ForceAngle, ForceLinear, SIM_GLOBAL_ACCELERATION end
local Vel = phys:GetVelocity()
local ForwardAngle = ent:GetDirectionAngle()
local Forward = ForwardAngle:Forward()
local Right = ForwardAngle:Right()
local Fy = self:VectorSplitNormal( Right, Vel )
local Fx = self:VectorSplitNormal( Forward, Vel )
if TorqueFactor >= 1 then
local VelX = math.abs( Fx )
local VelY = math.abs( Fy )
if VelY > VelX * 0.1 then
if VelX > EntTable.FastSteerActiveVelocity then
if VelY < VelX * 0.6 then
return ForceAngle, ForceLinear, SIM_GLOBAL_ACCELERATION
end
else
return ForceAngle, ForceLinear, SIM_GLOBAL_ACCELERATION
end
end
end
if IsBraking and not IsBrakingWheel then
return ForceAngle, ForceLinear, SIM_GLOBAL_ACCELERATION
end
ForceLinear:Add( -self:GetUp() * EntTable.WheelDownForce * TorqueFactor )
if not self:GetRacingTires() then
ForceLinear:Add( -Right * math.Clamp(Fy * 5 * math.min( math.abs( Fx ) / 500, 1 ),-EntTable.WheelSideForce,EntTable.WheelSideForce) * EntTable.ForceLinearMultiplier )
end
return ForceAngle, ForceLinear, SIM_GLOBAL_ACCELERATION
end
function ENT:SteerTo( TargetValue, MaxSteer )
local Cur = self:GetSteer() / MaxSteer
local Diff = TargetValue - Cur
local Returning = (Diff > 0 and Cur < 0) or (Diff < 0 and Cur > 0)
local Rate = FrameTime() * (Returning and self.SteerReturnSpeed or self.SteerSpeed)
local New = (Cur + math.Clamp(Diff,-Rate,Rate))
self:SetSteer( New * MaxSteer )
if New == 0 or self:GetEngineActive() then return end
for _, wheel in pairs( self:GetWheels() ) do
if not IsValid( wheel ) then continue end
wheel:PhysWake()
end
end
function ENT:OnDriverEnterVehicle( ply )
end
function ENT:OnDriverExitVehicle( ply )
end
function ENT:OnDriverChanged( Old, New, VehicleIsActive )
self:OnPassengerChanged( Old, New, 1 )
if VehicleIsActive then
self:OnDriverEnterVehicle( New )
return
end
self:OnDriverExitVehicle( Old )
self:SetThrottle( 0 )
if self:GetBrake() > 0 then
self:SetBrake( 0 )
self:EnableHandbrake()
self:StopEngine()
self:SetTurnMode( 0 )
local LightsHandler = self:GetLightsHandler()
if IsValid( LightsHandler ) then
LightsHandler:SetActive( false )
LightsHandler:SetHighActive( false )
LightsHandler:SetFogActive( false )
end
else
if not self:GetEngineActive() then
self:SetBrake( 0 )
self:EnableHandbrake()
end
end
self:SetReverse( false )
self:StopSiren()
if self:GetSirenMode() > 0 then
self:SetSirenMode( 0 )
end
if IsValid( self.HornSND ) then
self.HornSND:Stop()
end
end
function ENT:OnRefueled()
local FuelTank = self:GetFuelTank()
if not IsValid( FuelTank ) then return end
FuelTank:EmitSound( "vehicles/jetski/jetski_no_gas_start.wav" )
end
function ENT:OnMaintenance()
local FuelTank = self:GetFuelTank()
local Engine = self:GetEngine()
if IsValid( Engine ) then
Engine:SetHP( Engine:GetMaxHP() )
Engine:SetDestroyed( false )
end
if IsValid( FuelTank ) then
FuelTank:ExtinguishAndRepair()
if FuelTank:GetFuel() ~= 1 then
FuelTank:SetFuel( 1 )
self:OnRefueled()
end
end
for _, wheel in pairs( self:GetWheels() ) do
if not IsValid( wheel ) then continue end
wheel:SetHP( wheel:GetMaxHP() )
end
end
function ENT:OnSuperCharged( enable )
end
function ENT:OnTurboCharged( enable )
end
function ENT:ApproachTargetAngle( TargetAngle )
local pod = self:GetDriverSeat()
if not IsValid( pod ) then return end
local ang = pod:GetAngles()
ang:RotateAroundAxis( self:GetUp(), 90 )
local Forward = ang:Right()
local View = pod:WorldToLocalAngles( TargetAngle ):Forward()
local Reversed = false
if self:AngleBetweenNormal( View, ang:Forward() ) < 90 then
Reversed = self:GetReverse()
end
local LocalAngSteer = (self:AngleBetweenNormal( View, ang:Right() ) - 90) / self.MouseSteerAngle
local Steer = (math.min( math.abs( LocalAngSteer ), 1 ) ^ self.MouseSteerExponent * self:Sign( LocalAngSteer ))
self:SteerTo( Reversed and Steer or -Steer, self:GetMaxSteerAngle() )
end

View File

@@ -0,0 +1,37 @@
function ENT:CalcMainActivityPassenger( ply )
end
function ENT:CalcMainActivity( ply )
if ply ~= self:GetDriver() then return self:CalcMainActivityPassenger( ply ) end
if ply.m_bWasNoclipping then
ply.m_bWasNoclipping = nil
ply:AnimResetGestureSlot( GESTURE_SLOT_CUSTOM )
if CLIENT then
ply:SetIK( true )
end
end
ply.CalcIdeal = ACT_STAND
ply.CalcSeqOverride = ply:LookupSequence( "drive_jeep" )
return ply.CalcIdeal, ply.CalcSeqOverride
end
function ENT:UpdateAnimation( ply, velocity, maxseqgroundspeed )
ply:SetPlaybackRate( 1 )
if CLIENT then
if ply == self:GetDriver() then
ply:SetPoseParameter( "vehicle_steer", self:GetSteer() / self:GetMaxSteerAngle() )
ply:InvalidateBoneCache()
end
GAMEMODE:GrabEarAnimation( ply )
GAMEMODE:MouthMoveAnimation( ply )
end
return false
end

View File

@@ -0,0 +1,50 @@
function ENT:GetEyeTrace( trace_forward )
local startpos = self:LocalToWorld( self:OBBCenter() )
local pod = self:GetDriverSeat()
if IsValid( pod ) then
startpos = pod:LocalToWorld( pod:OBBCenter() )
end
local AimVector = trace_forward and self:GetForward() or self:GetAimVector()
local data = {
start = startpos,
endpos = (startpos + AimVector * 50000),
filter = self:GetCrosshairFilterEnts(),
}
local trace = util.TraceLine( data )
return trace
end
function ENT:GetAimVector()
if self:GetAI() then
return self:GetAIAimVector()
end
local pod = self:GetDriverSeat()
if not IsValid( pod ) then return self:GetForward() end
local Driver = self:GetDriver()
if not IsValid( Driver ) then return pod:GetForward() end
if Driver:lvsMouseAim() then
if SERVER then
return pod:WorldToLocalAngles( Driver:EyeAngles() ):Forward()
else
return Driver:EyeAngles():Forward()
end
else
if SERVER then
return Driver:EyeAngles():Forward()
else
return pod:LocalToWorldAngles( Driver:EyeAngles() ):Forward()
end
end
end

View File

@@ -0,0 +1,345 @@
ENT.Base = "lvs_base"
ENT.PrintName = "[LVS] Wheeldrive Base"
ENT.Author = "Luna"
ENT.Information = "Luna's Vehicle Script"
ENT.Category = "[LVS] - Cars"
ENT.Spawnable = false
ENT.AdminSpawnable = false
ENT.MaxHealth = 600
ENT.MaxHealthEngine = 50
ENT.MaxHealthFuelTank = 10
ENT.MaxVelocity = 1400
ENT.MaxVelocityReverse = 700
ENT.EngineCurve = 0.65
ENT.EngineCurveBoostLow = 1
ENT.EngineTorque = 350
ENT.EngineIdleRPM = 1000
ENT.EngineMaxRPM = 6000
ENT.ThrottleRate = 3.5
ENT.BrakeRate = 3.5
ENT.ForceLinearMultiplier = 1
ENT.ForceLinearMultiplierRacingTires = 1.15
ENT.ForceAngleMultiplier = 0.5
ENT.TransGears = 4
ENT.TransGearsReverse = 1
ENT.TransMinGearHoldTime = 1
ENT.TransShiftSpeed = 0.3
ENT.TransShiftTorqueFactor = 0
ENT.TransWobble = 40
ENT.TransWobbleTime = 1.5
ENT.TransWobbleFrequencyMultiplier = 1
ENT.TransShiftSound = "lvs/vehicles/generic/gear_shift.wav"
ENT.SteerSpeed = 3
ENT.SteerReturnSpeed = 10
ENT.FastSteerActiveVelocity = 500
ENT.FastSteerAngleClamp = 10
ENT.FastSteerDeactivationDriftAngle = 7
ENT.SteerAssistDeadZoneAngle = 1
ENT.SteerAssistMaxAngle = 15
ENT.SteerAssistExponent = 1.5
ENT.SteerAssistMultiplier = 3
ENT.MouseSteerAngle = 20
ENT.MouseSteerExponent = 2
ENT.PhysicsWeightScale = 1
ENT.PhysicsMass = 1000
ENT.PhysicsInertia = Vector(1500,1500,750)
ENT.PhysicsDampingSpeed = 4000
ENT.PhysicsDampingForward = true
ENT.PhysicsDampingReverse = false
ENT.WheelTickInterval = 0.2
ENT.WheelTickIntervalBraking = 0.02
ENT.WheelPhysicsMass = 100
ENT.WheelPhysicsInertia = Vector(10,8,10)
ENT.WheelPhysicsTireHeight = 4
ENT.WheelPhysicsMaterials = {
[0] = "friction_00", -- 0
[1] = "friction_10", -- 0.1
[2] = "friction_25", -- 0.25
[3] = "rubber", -- 0.8
[4] = "rubber",
[5] = "rubber",
[6] = "rubber",
[7] = "rubber",
[8] = "rubber",
[9] = "rubber",
[10] = "jeeptire", -- 1.337 -- i don't believe friction in havok can go above 1, however other settings such as bouncyness and elasticity are affected by it as it seems. We use jeeptire as default even tho it technically isn't the "best" choice, but rather the most common one
[11] = "jalopytire", -- 1.337
[12] = "phx_tire_normal", -- 3
}
ENT.AutoReverseVelocity = 50
ENT.WheelBrakeLockupRPM = 20
ENT.WheelBrakeForce = 400
ENT.WheelSideForce = 800
ENT.WheelDownForce = 500
ENT.AllowSuperCharger = true
ENT.SuperChargerVolume = 0.6
ENT.SuperChargerSound = "lvs/vehicles/generic/supercharger_loop.wav"
ENT.SuperChargerVisible = true
ENT.AllowTurbo = true
ENT.TurboVolume = 0.6
ENT.TurboSound = "lvs/vehicles/generic/turbo_loop.wav"
ENT.TurboBlowOff = {"lvs/vehicles/generic/turbo_blowoff1.wav","lvs/vehicles/generic/turbo_blowoff2.wav"}
ENT.DeleteOnExplode = false
ENT.lvsAllowEngineTool = true
ENT.lvsShowInSpawner = false
function ENT:SetupDataTables()
self:CreateBaseDT()
self:AddDT( "Float", "Steer" )
self:AddDT( "Float", "Throttle" )
self:AddDT( "Float", "MaxThrottle" )
self:AddDT( "Float", "Brake" )
self:AddDT( "Float", "NWMaxSteer" )
self:AddDT( "Float", "WheelVelocity" )
self:AddDT( "Int", "NWGear" )
self:AddDT( "Int", "TurnMode" )
self:AddDT( "Int", "SirenMode" )
self:AddDT( "Bool", "Reverse" )
self:AddDT( "Bool", "NWHandBrake" )
self:AddDT( "Bool", "RacingHud" )
self:AddDT( "Bool", "RacingTires" )
self:AddDT( "Bool", "Backfire" )
self:AddDT( "Entity", "Engine" )
self:AddDT( "Entity", "FuelTank" )
self:AddDT( "Entity", "LightsHandler" )
self:AddDT( "Entity", "Turbo" )
self:AddDT( "Entity", "Compressor" )
self:AddDT( "Vector", "AIAimVector" )
self:TrackSystemDT()
if SERVER then
self:SetMaxThrottle( 1 )
self:SetSirenMode( -1 )
end
end
function ENT:TrackSystemDT()
end
function ENT:StabilityAssist()
if self:GetReverse() then
return self.PhysicsDampingReverse
end
return self.PhysicsDampingForward
end
function ENT:GetMaxSteerAngle()
if CLIENT then return self:GetNWMaxSteer() end
local EntTable = self:GetTable()
if EntTable._WheelMaxSteerAngle then return EntTable._WheelMaxSteerAngle end
local Cur = 0
for _, Axle in pairs( EntTable._WheelAxleData ) do
if not Axle.SteerAngle then continue end
if Axle.SteerAngle > Cur then
Cur = Axle.SteerAngle
end
end
EntTable._WheelMaxSteerAngle = Cur
self:SetNWMaxSteer( Cur )
return Cur
end
function ENT:GetTargetVelocity()
local Reverse = self:GetReverse()
if self:IsManualTransmission() then
local Gear = self:GetGear()
local EntTable = self:GetTable()
local NumGears = Reverse and EntTable.TransGearsReverse or EntTable.TransGears
local MaxVelocity = Reverse and EntTable.MaxVelocityReverse or EntTable.MaxVelocity
local GearedVelocity = math.min( (MaxVelocity / NumGears) * (Gear + 1), MaxVelocity )
return GearedVelocity * (Reverse and -1 or 1)
end
if Reverse then
return -self.MaxVelocityReverse
end
return self.MaxVelocity
end
function ENT:HasHighBeams()
local EntTable = self:GetTable()
if isbool( EntTable._HasHighBeams ) then return EntTable._HasHighBeams end
if not istable( EntTable.Lights ) then return false end
local HasHigh = false
for _, data in pairs( EntTable.Lights ) do
if not istable( data ) then continue end
for id, typedata in pairs( data ) do
if id == "Trigger" and typedata == "high" then
HasHigh = true
break
end
end
end
EntTable._HasHighBeams = HasHigh
return HasHigh
end
function ENT:HasFogLights()
local EntTable = self:GetTable()
if isbool( EntTable._HasFogLights ) then return EntTable._HasFogLights end
if not istable( EntTable.Lights ) then return false end
local HasFog = false
for _, data in pairs( EntTable.Lights ) do
if not istable( data ) then continue end
for id, typedata in pairs( data ) do
if id == "Trigger" and typedata == "fog" then
HasFog = true
break
end
end
end
EntTable._HasFogLights = HasFog
return HasFog
end
function ENT:HasTurnSignals()
local EntTable = self:GetTable()
if isbool( EntTable._HasTurnSignals ) then return EntTable._HasTurnSignals end
if not istable( EntTable.Lights ) then return false end
local HasTurnSignals = false
for _, data in pairs( EntTable.Lights ) do
if not istable( data ) then continue end
for id, typedata in pairs( data ) do
if id == "Trigger" and (typedata == "turnleft" or typedata == "turnright" or typedata == "main+brake+turnleft" or typedata == "main+brake+turnright") then
HasTurnSignals = true
break
end
end
end
EntTable._HasTurnSignals = HasTurnSignals
return HasTurnSignals
end
function ENT:GetGear()
local Gear = self:GetNWGear()
if Gear <= 0 then
return -1
end
if self:GetReverse() then
return math.Clamp( Gear, 1, self.TransGearsReverse )
end
return math.Clamp( Gear, 1, self.TransGears )
end
function ENT:IsManualTransmission()
return self:GetNWGear() > 0
end
function ENT:BodygroupIsValid( name, groups )
if not name or not istable( groups ) then return false end
local EntTable = self:GetTable()
local id = -1
if EntTable._StoredBodyGroups then
if EntTable._StoredBodyGroups[ name ] then
id = EntTable._StoredBodyGroups[ name ]
end
else
EntTable._StoredBodyGroups = {}
end
if id == -1 then
for _, data in pairs( self:GetBodyGroups() ) do
if data.name == name then
id = data.id
break
end
end
end
if id == -1 then return false end
EntTable._StoredBodyGroups[ name ] = id
local cur = self:GetBodygroup( id )
for _, active in pairs( groups ) do
if cur == active then return true end
end
return false
end
function ENT:GetWheelUp()
return self:GetUp()
end
function ENT:GetVehicleType()
return "car"
end

View File

@@ -0,0 +1,254 @@
function ENT:OnCreateAI()
self:DisableManualTransmission()
self:StartEngine()
end
function ENT:OnRemoveAI()
self:StopEngine()
end
function ENT:AIGetMovementTarget()
local Pod = self:GetDriverSeat()
if not IsValid( Pod ) then return (self._MovmentTarget or self:GetPos()) end
return (self._MovmentTarget or Pod:LocalToWorld( Pod:OBBCenter() + Vector(0,100,0))), (self._MovementDistance or 500)
end
function ENT:AISetMovementTarget( pos, dist )
self._MovmentTarget = pos
self._MovementDistance = (dist or 500)
end
local DontChase = {
["starfighter"] = true,
["repulsorlift"] = true,
["plane"] = true,
["helicopter"] = true,
}
function ENT:RunAI()
local Pod = self:GetDriverSeat()
if not IsValid( Pod ) then self:SetAI( false ) return end
local RangerLength = 25000
local Target = self:AIGetTarget( 180 )
local StartPos = Pod:LocalToWorld( Pod:OBBCenter() )
local GotoPos, GotoDist = self:AIGetMovementTarget()
local TargetPos = GotoPos
local T = CurTime()
local IsTargetValid = IsValid( Target )
if IsTargetValid then
if self:AIHasWeapon( 1 ) then
if (self._LastGotoPos or 0) > T then
GotoPos = self._OldGotoPos
else
local Pos = Target:GetPos()
local Sub = self:GetPos() - Pos
local Dir = Sub:GetNormalized()
local Dist = Sub:Length()
GotoPos = Pos + Dir * math.min( 2000, Dist )
self._LastGotoPos = T + math.random(4,8)
self._OldGotoPos = GotoPos
end
else
GotoPos = Target:GetPos()
end
else
local TraceFilter = self:GetCrosshairFilterEnts()
local Front = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos + Pod:GetForward() * RangerLength } )
local FrontLeft = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos - Pod:LocalToWorldAngles( Angle(0,15,0) ):Right() * RangerLength } )
local FrontRight = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos - Pod:LocalToWorldAngles( Angle(0,-15,0) ):Right() * RangerLength } )
local FrontLeft1 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos - Pod:LocalToWorldAngles( Angle(0,60,0) ):Right() * RangerLength } )
local FrontRight1 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos - Pod:LocalToWorldAngles( Angle(0,-60,0) ):Right() * RangerLength } )
local FrontLeft2 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos - Pod:LocalToWorldAngles( Angle(0,85,0) ):Right() * RangerLength } )
local FrontRight2 = util.TraceLine( { start = StartPos, filter = TraceFilter, endpos = StartPos - Pod:LocalToWorldAngles( Angle(0,-85,0) ):Right() * RangerLength } )
local traceWater = util.TraceLine( {
start = Front.HitPos,
endpos = Front.HitPos - Vector(0,0,50000),
filter = self:GetCrosshairFilterEnts(),
mask = MASK_WATER
} )
if traceWater.Hit then
Front.HitPos = StartPos
end
GotoPos = (Front.HitPos + FrontLeft.HitPos + FrontRight.HitPos + FrontLeft1.HitPos + FrontRight1.HitPos + FrontLeft2.HitPos + FrontRight2.HitPos) / 7
if not self:GetEngineActive() then
local Engine = self:GetEngine()
if IsValid( Engine ) then
if not Engine:GetDestroyed() then
self:StartEngine()
end
else
self:StartEngine()
end
end
if self:GetReverse() then
if Front.Fraction < 0.03 then
GotoPos = StartPos - Pod:GetForward() * 1000
end
else
if Front.Fraction < 0.01 then
GotoPos = StartPos - Pod:GetForward() * 1000
end
end
end
local TargetPosLocal = Pod:WorldToLocal( GotoPos )
local Throttle = math.min( math.max( TargetPosLocal:Length() - GotoDist, 0 ) / 10, 1 )
self:PhysWake()
local PivotSteer = self.PivotSteerEnable
local DontMove = PivotSteer and Throttle == 0
if PivotSteer and IsTargetValid and Target.LVS and Target.GetVehicleType then
if DontChase[ Target:GetVehicleType() ] then
DontMove = true
end
end
if DontMove then
local ang = self:GetAngles()
ang.y = Pod:GetAngles().y + 90
local View = self:GetAimVector()
local LocalAngSteer = math.Clamp( (self:AngleBetweenNormal( View, ang:Right() ) - 90) / 10,-1,1)
Throttle = math.abs( LocalAngSteer ) ^ 2
if Throttle < 0.1 then
Throttle = 0
LocalAngSteer = 0
end
self:SetSteer( 0 )
self:SetPivotSteer( -LocalAngSteer )
self:LerpThrottle( Throttle )
self:LerpBrake( 0 )
else
self:SetPivotSteer( 0 )
if self:IsLegalInput() then
self:LerpThrottle( Throttle )
if Throttle == 0 then
self:LerpBrake( 1 )
else
self:LerpBrake( 0 )
end
else
self:LerpThrottle( 0 )
self:LerpBrake( Throttle )
end
self:SetReverse( TargetPosLocal.y < 0 )
self:ApproachTargetAngle( Pod:LocalToWorldAngles( (GotoPos - self:GetPos()):Angle() ) )
end
self:ReleaseHandbrake()
self._AIFireInput = false
if IsValid( self:GetHardLockTarget() ) then
Target = self:GetHardLockTarget()
TargetPos = Target:LocalToWorld( Target:OBBCenter() )
self._AIFireInput = true
else
if IsValid( Target ) then
local PhysObj = Target:GetPhysicsObject()
if IsValid( PhysObj ) then
TargetPos = Target:LocalToWorld( PhysObj:GetMassCenter() )
else
TargetPos = Target:LocalToWorld( Target:OBBCenter() )
end
if self:AIHasWeapon( 1 ) or self:AIHasWeapon( 2 ) then
self._AIFireInput = true
end
local CurHeat = self:GetNWHeat()
local CurWeapon = self:GetSelectedWeapon()
if CurWeapon > 2 then
self:AISelectWeapon( 1 )
else
if CurHeat < 0.9 then
if CurWeapon == 1 and self:AIHasWeapon( 2 ) then
self:AISelectWeapon( 2 )
elseif CurWeapon == 2 then
self:AISelectWeapon( 1 )
end
else
if CurHeat == 0 and math.cos( CurTime() ) > 0 then
self:AISelectWeapon( 1 )
end
end
end
end
end
T = T + self:EntIndex() * 1.337
self:SetAIAimVector( (TargetPos + Vector(0,math.sin( T * 0.5 ) * 30,math.cos( T * 2 ) * 30) - StartPos):GetNormalized() )
end
function ENT:OnAITakeDamage( dmginfo )
local attacker = dmginfo:GetAttacker()
if not IsValid( attacker ) then return end
if not self:AITargetInFront( attacker, IsValid( self:AIGetTarget() ) and 120 or 45 ) then
self:SetHardLockTarget( attacker )
end
end
function ENT:SetHardLockTarget( target )
if not self:IsEnemy( target ) then return end
self._HardLockTarget = target
self._HardLockTime = CurTime() + 4
end
function ENT:GetHardLockTarget()
if (self._HardLockTime or 0) < CurTime() then return NULL end
return self._HardLockTarget
end
function ENT:AISelectWeapon( ID )
if ID == self:GetSelectedWeapon() then return end
local T = CurTime()
if (self._nextAISwitchWeapon or 0) > T then return end
self._nextAISwitchWeapon = T + math.random(3,6)
self:SelectWeapon( ID )
end

View File

@@ -0,0 +1,211 @@
function ENT:AddEngine( pos, ang, mins, maxs )
if IsValid( self:GetEngine() ) then return end
ang = ang or angle_zero
mins = mins or Vector(-10,-10,-10)
maxs = maxs or Vector(10,10,10)
local Engine = ents.Create( "lvs_wheeldrive_engine" )
if not IsValid( Engine ) then
self:Remove()
print("LVS: Failed to create engine entity. Vehicle terminated.")
return
end
Engine:SetPos( self:LocalToWorld( pos ) )
Engine:SetAngles( self:LocalToWorldAngles( ang ) )
Engine:Spawn()
Engine:Activate()
Engine:SetParent( self )
Engine:SetBase( self )
Engine:SetMaxHP( self.MaxHealthEngine )
Engine:SetHP( self.MaxHealthEngine )
self:SetEngine( Engine )
self:DeleteOnRemove( Engine )
self:TransferCPPI( Engine )
debugoverlay.BoxAngles( self:LocalToWorld( pos ), mins, maxs, self:LocalToWorldAngles( ang ), 15, Color( 0, 255, 255, 255 ) )
self:AddDS( {
pos = pos,
ang = ang,
mins = mins,
maxs = maxs,
Callback = function( tbl, ent, dmginfo )
local Engine = self:GetEngine()
if not IsValid( Engine ) then return end
Engine:TakeTransmittedDamage( dmginfo )
if not Engine:GetDestroyed() then
dmginfo:ScaleDamage( 0.25 )
end
end
} )
return Engine
end
function ENT:AddFuelTank( pos, ang, tanksize, fueltype, mins, maxs )
if IsValid( self:GetFuelTank() ) then return end
local FuelTank = ents.Create( "lvs_wheeldrive_fueltank" )
if not IsValid( FuelTank ) then
self:Remove()
print("LVS: Failed to create fueltank entity. Vehicle terminated.")
return
end
FuelTank:SetPos( self:LocalToWorld( pos ) )
FuelTank:SetAngles( self:GetAngles() )
FuelTank:Spawn()
FuelTank:Activate()
FuelTank:SetParent( self )
FuelTank:SetBase( self )
FuelTank:SetSize( tanksize or 600 )
FuelTank:SetFuelType( fueltype or 0 )
FuelTank:SetMaxHP( self.MaxHealthFuelTank )
FuelTank:SetHP( self.MaxHealthFuelTank )
self:SetFuelTank( FuelTank )
self:DeleteOnRemove( FuelTank )
self:TransferCPPI( FuelTank )
mins = mins or Vector(-15,-15,-5)
maxs = maxs or Vector(15,15,5)
debugoverlay.BoxAngles( self:LocalToWorld( pos ), mins, maxs, self:LocalToWorldAngles( ang ), 15, Color( 255, 255, 0, 255 ) )
self:AddDS( {
pos = pos,
ang = ang,
mins = mins,
maxs = maxs,
Callback = function( tbl, ent, dmginfo )
if not IsValid( FuelTank ) then return end
FuelTank:TakeTransmittedDamage( dmginfo )
if not FuelTank:GetDestroyed() then
dmginfo:ScaleDamage( 0.25 )
end
end
} )
return FuelTank
end
function ENT:AddLights()
if IsValid( self:GetLightsHandler() ) then return end
local LightHandler = ents.Create( "lvs_wheeldrive_lighthandler" )
if not IsValid( LightHandler ) then return end
LightHandler:SetPos( self:LocalToWorld( self:OBBCenter() ) )
LightHandler:SetAngles( self:GetAngles() )
LightHandler:Spawn()
LightHandler:Activate()
LightHandler:SetParent( self )
LightHandler:SetBase( self )
self:DeleteOnRemove( LightHandler )
self:TransferCPPI( LightHandler )
self:SetLightsHandler( LightHandler )
end
function ENT:AddTurboCharger()
local Ent = self:GetTurbo()
if IsValid( Ent ) then return Ent end
local Turbo = ents.Create( "lvs_item_turbo" )
if not IsValid( Turbo ) then
self:Remove()
print("LVS: Failed to create turbocharger entity. Vehicle terminated.")
return
end
Turbo:SetPos( self:GetPos() )
Turbo:SetAngles( self:GetAngles() )
Turbo:Spawn()
Turbo:Activate()
Turbo:LinkTo( self )
self:TransferCPPI( Turbo )
return Turbo
end
function ENT:AddSuperCharger()
local Ent = self:GetCompressor()
if IsValid( Ent ) then return Ent end
local SuperCharger = ents.Create( "lvs_item_compressor" )
if not IsValid( SuperCharger ) then
self:Remove()
print("LVS: Failed to create supercharger entity. Vehicle terminated.")
return
end
SuperCharger:SetPos( self:GetPos() )
SuperCharger:SetAngles( self:GetAngles() )
SuperCharger:Spawn()
SuperCharger:Activate()
SuperCharger:LinkTo( self )
self:TransferCPPI( SuperCharger )
return SuperCharger
end
function ENT:AddDriverViewPort( pos, ang, mins, maxs )
self:AddDS( {
pos = pos,
ang = ang,
mins = mins,
maxs = maxs,
Callback = function( tbl, ent, dmginfo )
if dmginfo:GetDamage() <= 0 then return end
local ply = self:GetDriver()
if IsValid( ply ) then
ply:EmitSound("lvs/hitdriver"..math.random(1,2)..".wav",120)
self:HurtPlayer( ply, dmginfo:GetDamage(), dmginfo:GetAttacker(), dmginfo:GetInflictor() )
end
dmginfo:ScaleDamage( 0 )
end
} )
end
function ENT:AddTuningExhaust()
self:SetBackfire( true )
end
function ENT:AddRacingTires()
self:SetRacingTires( true )
end

View File

@@ -0,0 +1,454 @@
function ENT:CalcMouseSteer( ply )
self:ApproachTargetAngle( ply:EyeAngles() )
end
function ENT:CalcSteer( ply )
local KeyLeft = ply:lvsKeyDown( "CAR_STEER_LEFT" )
local KeyRight = ply:lvsKeyDown( "CAR_STEER_RIGHT" )
local MaxSteer = self:GetMaxSteerAngle()
local Vel = self:GetVelocity()
local TargetValue = (KeyRight and 1 or 0) - (KeyLeft and 1 or 0)
local EntTable = self:GetTable()
if Vel:Length() > EntTable.FastSteerActiveVelocity then
local Forward = self:GetForward()
local Right = self:GetRight()
local Axle = self:GetAxleData( 1 )
if Axle then
local Ang = self:LocalToWorldAngles( self:GetAxleData( 1 ).ForwardAngle )
Forward = Ang:Forward()
Right = Ang:Right()
end
local VelNormal = Vel:GetNormalized()
local DriftAngle = self:AngleBetweenNormal( Forward, VelNormal )
if self:GetRacingTires() or self:GetBrake() >= 1 then
if math.abs( self:GetSteer() ) < EntTable.FastSteerAngleClamp then
MaxSteer = math.min( MaxSteer, EntTable.FastSteerAngleClamp )
end
else
if DriftAngle < EntTable.FastSteerDeactivationDriftAngle then
MaxSteer = math.min( MaxSteer, EntTable.FastSteerAngleClamp )
end
end
if not KeyLeft and not KeyRight then
local Cur = self:GetSteer() / MaxSteer
local MaxHelpAng = math.min( MaxSteer, EntTable.SteerAssistMaxAngle )
local Ang = self:AngleBetweenNormal( Right, VelNormal ) - 90
local HelpAng = ((math.abs( Ang ) / 90) ^ EntTable.SteerAssistExponent) * 90 * self:Sign( Ang )
TargetValue = math.Clamp( -HelpAng * EntTable.SteerAssistMultiplier,-MaxHelpAng,MaxHelpAng) / MaxSteer
end
end
self:SteerTo( TargetValue, MaxSteer )
end
function ENT:IsLegalInput()
local EntTable = self:GetTable()
if not EntTable.ForwardAngle then return true end
local MinSpeed = math.min(EntTable.MaxVelocity,EntTable.MaxVelocityReverse)
local ForwardVel = self:Sign( math.Round( self:VectorSplitNormal( self:LocalToWorldAngles( EntTable.ForwardAngle ):Forward(), self:GetVelocity() ) / MinSpeed, 0 ) )
local DesiredVel = self:GetReverse() and -1 or 1
return ForwardVel == DesiredVel * math.abs( ForwardVel )
end
function ENT:LerpThrottle( Throttle )
if not self:GetEngineActive() then self:SetThrottle( 0 ) return end
local FT = FrameTime()
local RateUp = self.ThrottleRate * FT
local RateDn = 3.5 * FT
local Cur = self:GetThrottle()
local New = Cur + math.Clamp(Throttle - Cur,-RateDn,RateUp)
self:SetThrottle( New )
end
function ENT:LerpBrake( Brake )
local FT = FrameTime()
local RateUp = self.BrakeRate * FT
local RateDn = 3.5 * FT
local Cur = self:GetBrake()
local New = Cur + math.Clamp(Brake - Cur,-RateDn,RateUp)
self:SetBrake( New )
end
function ENT:CalcThrottle( ply )
local KeyThrottle = ply:lvsKeyDown( "CAR_THROTTLE" )
local KeyBrakes = ply:lvsKeyDown( "CAR_BRAKE" )
if self:GetReverse() and not self:IsManualTransmission() then
KeyThrottle = ply:lvsKeyDown( "CAR_BRAKE" )
KeyBrakes = ply:lvsKeyDown( "CAR_THROTTLE" )
end
local ThrottleValue = ply:lvsKeyDown( "CAR_THROTTLE_MOD" ) and self:GetMaxThrottle() or 0.5
local Throttle = KeyThrottle and ThrottleValue or 0
if not self:IsLegalInput() then
self:LerpThrottle( 0 )
self:LerpBrake( (KeyThrottle or KeyBrakes) and 1 or 0 )
return
end
self:LerpThrottle( Throttle )
self:LerpBrake( KeyBrakes and 1 or 0 )
end
function ENT:CalcHandbrake( ply )
if ply:lvsKeyDown( "CAR_HANDBRAKE" ) then
self:EnableHandbrake()
else
self:ReleaseHandbrake()
end
end
function ENT:CalcTransmission( ply, T )
local EntTable = self:GetTable()
if not EntTable.ForwardAngle or self:IsManualTransmission() then
local ShiftUp = ply:lvsKeyDown( "CAR_SHIFT_UP" )
local ShiftDn = ply:lvsKeyDown( "CAR_SHIFT_DN" )
self:CalcManualTransmission( ply, EntTable, ShiftUp, ShiftDn )
local Reverse = self:GetReverse()
if Reverse ~= EntTable._oldKeyReverse then
EntTable._oldKeyReverse = Reverse
self:EmitSound( EntTable.TransShiftSound, 75 )
end
return
end
local ForwardVelocity = self:VectorSplitNormal( self:LocalToWorldAngles( EntTable.ForwardAngle ):Forward(), self:GetVelocity() )
local KeyForward = ply:lvsKeyDown( "CAR_THROTTLE" )
local KeyBackward = ply:lvsKeyDown( "CAR_BRAKE" )
local ReverseVelocity = EntTable.AutoReverseVelocity
if KeyForward and KeyBackward then return end
if not KeyForward and not KeyBackward then
if ForwardVelocity > ReverseVelocity then
self:SetReverse( false )
end
if ForwardVelocity < -ReverseVelocity then
self:SetReverse( true )
end
return
end
if KeyForward and ForwardVelocity > -ReverseVelocity then
self:SetReverse( false )
end
if KeyBackward and ForwardVelocity < ReverseVelocity then
if not EntTable._toggleReverse then
EntTable._toggleReverse = true
EntTable._KeyBackTime = T + 0.4
end
if (EntTable._KeyBackTime or 0) < T then
self:SetReverse( true )
end
else
EntTable._toggleReverse = nil
end
local Reverse = self:GetReverse()
if Reverse ~= EntTable._oldKeyReverse then
EntTable._oldKeyReverse = Reverse
self:EmitSound( EntTable.TransShiftSound, 75 )
end
end
function ENT:CalcLights( ply, T )
local LightsHandler = self:GetLightsHandler()
if not IsValid( LightsHandler ) then return end
local lights = ply:lvsKeyDown( "CAR_LIGHTS_TOGGLE" )
local EntTable = self:GetTable()
if EntTable._lights ~= lights then
EntTable._lights = lights
if lights then
EntTable._LightsUnpressTime = T
else
EntTable._LightsUnpressTime = nil
end
end
if EntTable._lights and (T - EntTable._LightsUnpressTime) > 0.4 then
lights = false
end
if lights ~= EntTable._oldlights then
if not isbool( EntTable._oldlights ) then EntTable._oldlights = lights return end
if lights then
EntTable._LightsPressedTime = T
else
if LightsHandler:GetActive() then
if self:HasHighBeams() then
if (T - (EntTable._LightsPressedTime or 0)) >= 0.4 then
LightsHandler:SetActive( false )
LightsHandler:SetHighActive( false )
LightsHandler:SetFogActive( false )
self:EmitSound( "items/flashlight1.wav", 75, 100, 0.25 )
else
LightsHandler:SetHighActive( not LightsHandler:GetHighActive() )
self:EmitSound( "buttons/lightswitch2.wav", 75, 80, 0.25)
end
else
LightsHandler:SetActive( false )
LightsHandler:SetHighActive( false )
LightsHandler:SetFogActive( false )
self:EmitSound( "items/flashlight1.wav", 75, 100, 0.25 )
end
else
self:EmitSound( "items/flashlight1.wav", 75, 100, 0.25 )
if self:HasFogLights() and (T - (EntTable._LightsPressedTime or T)) >= 0.4 then
LightsHandler:SetFogActive( not LightsHandler:GetFogActive() )
else
LightsHandler:SetActive( true )
end
end
end
EntTable._oldlights = lights
end
end
function ENT:StartCommand( ply, cmd )
if self:GetDriver() ~= ply then return end
local EntTable = self:GetTable()
self:SetRoadkillAttacker( ply )
if ply:lvsKeyDown( "CAR_MENU" ) then
self:LerpBrake( 0 )
self:LerpThrottle( 0 )
return
end
self:UpdateHydraulics( ply, cmd )
if ply:lvsMouseAim() then
if ply:lvsKeyDown( "FREELOOK" ) or ply:lvsKeyDown( "CAR_STEER_LEFT" ) or ply:lvsKeyDown( "CAR_STEER_RIGHT" ) then
self:CalcSteer( ply )
else
self:CalcMouseSteer( ply )
end
else
self:CalcSteer( ply )
end
if EntTable.PivotSteerEnable then
self:CalcPivotSteer( ply )
if self:PivotSteer() then
self:LerpBrake( 0 )
else
self:CalcThrottle( ply )
end
else
self:CalcThrottle( ply )
end
local T = CurTime()
if (EntTable._nextCalcCMD or 0) > T then return end
EntTable._nextCalcCMD = T + FrameTime() - 1e-4
self:CalcHandbrake( ply )
self:CalcTransmission( ply, T )
self:CalcLights( ply, T )
self:CalcSiren( ply, T )
end
function ENT:CalcSiren( ply, T )
local mode = self:GetSirenMode()
local horn = ply:lvsKeyDown( "ATTACK" ) and not ply:lvsKeyDown( "ZOOM" )
local EntTable = self:GetTable()
if EntTable.HornSound and IsValid( EntTable.HornSND ) then
if horn and mode <= 0 then
EntTable.HornSND:Play()
else
EntTable.HornSND:Stop()
end
end
if istable( EntTable.SirenSound ) and IsValid( EntTable.SirenSND ) then
local siren = ply:lvsKeyDown( "CAR_SIREN" )
if EntTable._siren ~= siren then
EntTable._siren = siren
if siren then
EntTable._sirenUnpressTime = T
else
EntTable._sirenUnpressTime = nil
end
end
if EntTable._siren and (T - EntTable._sirenUnpressTime) > 0.4 then
siren = false
end
if siren ~= EntTable._oldsiren then
if not isbool( EntTable._oldsiren ) then EntTable._oldsiren = siren return end
if siren then
EntTable._SirenPressedTime = T
else
if (T - (EntTable._SirenPressedTime or 0)) >= 0.4 then
if mode >= 0 then
self:SetSirenMode( -1 )
self:StopSiren()
else
self:SetSirenMode( 0 )
end
else
self:StartSiren( horn, true )
end
end
EntTable._oldsiren = siren
else
if horn ~= EntTable._OldKeyHorn then
EntTable._OldKeyHorn = horn
if horn then
self:StartSiren( true, false )
else
self:StartSiren( false, false )
end
end
end
end
end
function ENT:SetSirenSound( sound )
if sound then
if self._PreventSiren then return end
self._PreventSiren = true
self.SirenSND:Stop()
self.SirenSND:SetSound( sound )
self.SirenSND:SetSoundInterior( sound )
timer.Simple( 0.1, function()
if not IsValid( self.SirenSND ) then return end
self.SirenSND:Play()
self._PreventSiren = false
end )
else
self:StopSiren()
end
end
function ENT:StartSiren( horn, incr )
local EntTable = self:GetTable()
local Mode = self:GetSirenMode()
local Max = #EntTable.SirenSound
local Next = Mode
if incr then
Next = Next + 1
if Mode <= -1 or Next > Max then
Next = 1
end
self:SetSirenMode( Next )
end
if not EntTable.SirenSound[ Next ] then return end
if horn then
if not EntTable.SirenSound[ Next ].horn then
self:SetSirenMode( 0 )
return
end
self:SetSirenSound( EntTable.SirenSound[ Next ].horn )
else
if not EntTable.SirenSound[ Next ].siren then
self:SetSirenMode( 0 )
return
end
self:SetSirenSound( EntTable.SirenSound[ Next ].siren )
end
end
function ENT:StopSiren()
if not IsValid( self.SirenSND ) then return end
self.SirenSND:Stop()
end
function ENT:SetRoadkillAttacker( ply )
local T = CurTime()
if (self._nextSetAttacker or 0) > T then return end
self._nextSetAttacker = T + 1
self:SetPhysicsAttacker( ply, 1.1 )
end

View File

@@ -0,0 +1,55 @@
function ENT:EnableHandbrake()
if self:IsHandbrakeActive() then return end
self:SetNWHandBrake( true )
self._HandbrakeEnabled = true
for _, Wheel in pairs( self:GetWheels() ) do
if not self:GetAxleData( Wheel:GetAxle() ).UseHandbrake then continue end
Wheel:SetHandbrake( true )
end
self:OnHandbrakeActiveChanged( true )
end
function ENT:ReleaseHandbrake()
if not self:IsHandbrakeActive() then return end
self:SetNWHandBrake( false )
self._HandbrakeEnabled = nil
for _, Wheel in pairs( self:GetWheels() ) do
if not self:GetAxleData( Wheel:GetAxle() ).UseHandbrake then continue end
Wheel:SetHandbrake( false )
end
self:OnHandbrakeActiveChanged( false )
end
function ENT:SetHandbrake( enable )
if enable then
self:EnableHandbrake()
return
end
self:ReleaseHandbrake()
end
function ENT:IsHandbrakeActive()
return self._HandbrakeEnabled == true
end
function ENT:OnHandbrakeActiveChanged( Active )
if Active then
self:EmitSound( "lvs/vehicles/generic/handbrake_on.wav", 75, 100, 0.25 )
else
self:EmitSound( "lvs/vehicles/generic/handbrake_off.wav", 75, 100, 0.25 )
end
end

View File

@@ -0,0 +1,139 @@
ENT.FireTrailScale = 0.35
ENT.DSArmorBulletPenetrationAdd = 50
DEFINE_BASECLASS( "lvs_base" )
function ENT:OnTakeDamage( dmginfo )
self.LastAttacker = dmginfo:GetAttacker()
self.LastInflictor = dmginfo:GetInflictor()
BaseClass.OnTakeDamage( self, dmginfo )
end
function ENT:TakeCollisionDamage( damage, attacker )
if not IsValid( attacker ) then
attacker = game.GetWorld()
end
local Engine = self:GetEngine()
if not IsValid( Engine ) then return end
local dmginfo = DamageInfo()
dmginfo:SetDamage( (math.min(damage / 4000,1) ^ 2) * 200 )
dmginfo:SetAttacker( attacker )
dmginfo:SetInflictor( attacker )
dmginfo:SetDamageType( DMG_CRUSH + DMG_VEHICLE )
Engine:TakeTransmittedDamage( dmginfo )
end
function ENT:Explode()
if self.ExplodedAlready then return end
self.ExplodedAlready = true
local Driver = self:GetDriver()
if IsValid( Driver ) then
self:HurtPlayer( Driver, 1000, self.FinalAttacker, self.FinalInflictor )
end
if istable( self.pSeats ) then
for _, pSeat in pairs( self.pSeats ) do
if not IsValid( pSeat ) then continue end
local psgr = pSeat:GetDriver()
if not IsValid( psgr ) then continue end
self:HurtPlayer( psgr, 1000, self.FinalAttacker, self.FinalInflictor )
end
end
self:OnFinishExplosion()
if self.DeleteOnExplode or self.SpawnedByAISpawner then
self:Remove()
return
end
if self.MDL_DESTROYED then
local numpp = self:GetNumPoseParameters() - 1
local pps = {}
for i = 0, numpp do
local sPose = self:GetPoseParameterName( i )
pps[ sPose ] = self:GetPoseParameter( sPose )
end
self:SetModel( self.MDL_DESTROYED )
self:PhysicsDestroy()
self:PhysicsInit( SOLID_VPHYSICS )
for pName, pValue in pairs( pps ) do
self:SetPoseParameter(pName, pValue)
end
else
for id, group in pairs( self:GetBodyGroups() ) do
for subid, subgroup in pairs( group.submodels ) do
if subgroup == "" or string.lower( subgroup ) == "empty" then
self:SetBodygroup( id - 1, subid )
end
end
end
end
for _, ent in pairs( self:GetCrosshairFilterEnts() ) do
if not IsValid( ent ) or ent == self then continue end
ent:Remove()
end
for _, ent in pairs( self:GetChildren() ) do
if not IsValid( ent ) then continue end
ent:Remove()
end
self:SetDriver( NULL )
self:RemoveWeapons()
self:StopMotionController()
self.DoNotDuplicate = true
self:OnExploded()
end
function ENT:RemoveWeapons()
self:WeaponsFinish()
for _, pod in pairs( self:GetPassengerSeats() ) do
local weapon = pod:lvsGetWeapon()
if not IsValid( weapon ) then continue end
weapon:WeaponsFinish()
end
self:WeaponsOnRemove()
for id, _ in pairs( self.WEAPONS ) do
self.WEAPONS[ id ] = {}
end
end
function ENT:OnExploded()
self:Ignite( 30 )
local PhysObj = self:GetPhysicsObject()
if not IsValid( PhysObj ) then return end
PhysObj:SetVelocity( self:GetVelocity() + Vector(math.random(-5,5),math.random(-5,5),math.random(150,250)) )
end

View File

@@ -0,0 +1,149 @@
DEFINE_BASECLASS( "lvs_base" )
function ENT:IsEngineStartAllowed()
if hook.Run( "LVS.IsEngineStartAllowed", self ) == false then return false end
if self:WaterLevel() > self.WaterLevelPreventStart then return false end
local FuelTank = self:GetFuelTank()
if IsValid( FuelTank ) and FuelTank:GetFuel() <= 0 then return false end
local Engine = self:GetEngine()
if IsValid( Engine ) and Engine:GetDestroyed() then
Engine:EmitSound( "lvs/vehicles/generic/gear_grind"..math.random(1,6)..".ogg", 75, math.Rand(70,100), 0.25 )
return false
end
return true
end
function ENT:StartEngine()
for _, wheel in pairs( self:GetWheels() ) do
if not IsValid( wheel ) then continue end
wheel:PhysWake()
end
BaseClass.StartEngine( self )
end
function ENT:OnEngineStalled()
timer.Simple(math.Rand(0.8,1.6), function()
if not IsValid( self ) or self:GetEngineActive() then return end
self:StartEngine()
end)
end
function ENT:StallEngine()
self:StopEngine()
if self:GetNWGear() ~= -1 then
self:SetNWGear( 1 )
end
self:OnEngineStalled()
end
function ENT:ShutDownEngine()
if not self:GetEngineActive() then return end
self:SetThrottle( 0 )
self:StopEngine()
end
function ENT:GetEngineTorque()
local EntTable = self:GetTable()
local T = CurTime()
if self:IsManualTransmission() then
local Gear = self:GetGear()
local NumGears = Reverse and EntTable.TransGearsReverse or EntTable.TransGears
local MaxVelocity = Reverse and EntTable.MaxVelocityReverse or EntTable.MaxVelocity
local PitchValue = MaxVelocity / NumGears
local Vel = self:GetVelocity():Length()
local preRatio = math.Clamp(Vel / (PitchValue * (Gear - 1)),0,1)
local Ratio = math.Clamp( 2 - math.max( Vel - PitchValue * (Gear - 1), 0 ) / PitchValue, 0, 1 )
local RatioIdeal = math.min( Ratio, preRatio )
if Gear < NumGears and Ratio < 0.5 and not EntTable.EngineRevLimited then
local engine = self:GetEngine()
if IsValid( engine ) and Ratio < (0.5 * self:GetThrottle()) then
local dmginfo = DamageInfo()
dmginfo:SetDamage( 1 )
dmginfo:SetAttacker( self )
dmginfo:SetInflictor( self )
dmginfo:SetDamageType( DMG_DIRECT )
engine:TakeTransmittedDamage( dmginfo )
end
end
if preRatio <= 0.05 and Vel < PitchValue and Gear > 1 then
self:SetNWGear( 1 )
end
if EntTable.TransShiftSpeed > 0.5 then
if EntTable._OldTorqueShiftGear ~= Gear then
if (EntTable._OldTorqueShiftGear or 0) < Gear then
EntTable._TorqueShiftDelayTime = T + math.max( EntTable.TransShiftSpeed - 0.5, 0 )
end
EntTable._OldTorqueShiftGear = Gear
end
end
if (EntTable._TorqueShiftDelayTime or 0) > T then return 0 end
return math.deg( self.EngineTorque ) * RatioIdeal
end
if EntTable.TransShiftSpeed > 0.5 and (EntTable._OldTorqueHoldGear or 0) < T then
local Reverse = self:GetReverse()
local vehVel = self:GetVelocity():Length()
local wheelVel = self:GetWheelVelocity()
local NumGears = EntTable.TransGears
local MaxGear = Reverse and EntTable.TransGearsReverse or NumGears
local PitchValue = EntTable.MaxVelocity / NumGears
local DesiredGear = 1
local VelocityGeared = vehVel
while (VelocityGeared > PitchValue) and DesiredGear< NumGears do
VelocityGeared = VelocityGeared - PitchValue
DesiredGear = DesiredGear + 1
end
if EntTable._OldTorqueShiftGear ~= DesiredGear then
if not EntTable._OldTorqueShiftGear then
EntTable._OldTorqueShiftGear = DesiredGear
else
if (EntTable._OldTorqueShiftGear or 0) < DesiredGear then
EntTable._TorqueShiftDelayTime = T + EntTable.TransShiftSpeed
end
EntTable._OldTorqueHoldGear = T + EntTable.TransShiftSpeed + EntTable.TransMinGearHoldTime
EntTable._OldTorqueShiftGear = DesiredGear
end
end
end
if (EntTable._TorqueShiftDelayTime or 0) > T then return math.deg( EntTable.EngineTorque ) * EntTable.TransShiftTorqueFactor end
return math.deg( EntTable.EngineTorque )
end

View File

@@ -0,0 +1,105 @@
function ENT:UpdateHydraulics( ply, cmd )
if not self._HydraulicControlers then return end
local all = ply:lvsKeyDown( "CAR_HYDRAULIC" )
if self._HydToggleAll ~= all then
self._HydToggleAll = all
if all then
self._HydToggleHeight = not self._HydToggleHeight
end
end
local HeightAll = self._HydToggleHeight and 1 or 0
local Invert = self._HydToggleHeight and -1 or 1
local FRONT = ply:lvsKeyDown( "CAR_HYDRAULIC_FRONT" )
local REAR = ply:lvsKeyDown( "CAR_HYDRAULIC_REAR" )
local LEFT = ply:lvsKeyDown( "CAR_HYDRAULIC_LEFT" )
local RIGHT = ply:lvsKeyDown( "CAR_HYDRAULIC_RIGHT" )
local FL = (FRONT or LEFT) and Invert or 0
local FR = (FRONT or RIGHT) and Invert or 0
local RL = (REAR or LEFT) and Invert or 0
local RR = (REAR or RIGHT) and Invert or 0
local HeightType = {
[""] = HeightAll,
["fl"] = HeightAll + FL,
["fr"] = HeightAll + FR,
["rl"] = HeightAll + RL,
["rr"] = HeightAll + RR,
}
local Rate = FrameTime() * 10
for _, control in ipairs( self._HydraulicControlers ) do
local curHeight = control:GetHeight()
local desHeight = HeightType[ control:GetType() ]
if curHeight == desHeight then control:OnFinish() continue end
control:SetHeight( curHeight + math.Clamp(desHeight - curHeight,-Rate,Rate) )
end
end
local HYD = {}
HYD.__index = HYD
function HYD:Initialize()
end
function HYD:GetHeight()
if not IsValid( self._WheelEntity ) then return 0 end
return self._WheelEntity:GetSuspensionHeight()
end
function HYD:SetHeight( new )
if not IsValid( self._WheelEntity ) then return end
self:OnStart()
self._WheelEntity:SetSuspensionHeight( new )
end
function HYD:OnStart()
if self.IsUpdatingHeight then return end
self.IsUpdatingHeight = true
if not IsValid( self._BaseEntity ) then return end
if self:GetHeight() > 0.5 then
self._BaseEntity:EmitSound("lvs/vehicles/generic/vehicle_hydraulic_down.ogg", 75, 100, 0.5, CHAN_WEAPON)
else
self._BaseEntity:EmitSound("lvs/vehicles/generic/vehicle_hydraulic_up.ogg", 75, 100, 0.5, CHAN_WEAPON)
end
end
function HYD:OnFinish()
if not self.IsUpdatingHeight then return end
self.IsUpdatingHeight = nil
if not IsValid( self._WheelEntity ) then return end
self._WheelEntity:EmitSound("lvs/vehicles/generic/vehicle_hydraulic_collide"..math.random(1,2)..".ogg", 75, 100, 0.5)
end
function HYD:GetType()
return self._WheelType
end
function ENT:CreateHydraulicControler( type, wheel )
if not istable( self._HydraulicControlers ) then
self._HydraulicControlers = {}
end
local controller = {}
setmetatable( controller, HYD )
controller._BaseEntity = self
controller._WheelEntity = wheel
controller._WheelType = type or ""
controller:Initialize()
table.insert( self._HydraulicControlers, controller )
end

View File

@@ -0,0 +1,72 @@
function ENT:EnableManualTransmission()
self:SetReverse( false )
self:SetNWGear( 1 )
end
function ENT:DisableManualTransmission()
self:SetNWGear( -1 )
end
function ENT:CalcManualTransmission( ply, EntTable, ShiftUp, ShiftDn )
if ShiftUp ~= EntTable._oldShiftUp then
EntTable._oldShiftUp = ShiftUp
if ShiftUp then
self:ShiftUp()
end
end
if ShiftDn ~= EntTable._oldShiftDn then
EntTable._oldShiftDn = ShiftDn
if ShiftDn then
self:ShiftDown()
end
end
end
function ENT:OnShiftUp()
end
function ENT:OnShiftDown()
end
function ENT:ShiftUp()
if self:OnShiftUp() == false then return end
local Reverse = self:GetReverse()
if Reverse then
local NextGear = self:GetNWGear() - 1
self:SetNWGear( math.max( NextGear, 1 ) )
if NextGear <= 0 then
self:SetReverse( false )
end
return
end
self:SetNWGear( math.min( self:GetNWGear() + 1, self.TransGears ) )
end
function ENT:ShiftDown()
if self:OnShiftDown() == false then return end
local Reverse = self:GetReverse()
if Reverse then
self:SetNWGear( math.min( self:GetNWGear() + 1, self.TransGearsReverse ) )
return
end
local NextGear = self:GetNWGear() - 1
self:SetNWGear( math.max( NextGear, 1 ) )
if NextGear <= 0 then
self:SetReverse( true )
end
end

View File

@@ -0,0 +1,58 @@
ENT.PivotSteerEnable = false
ENT.PivotSteerByBrake = true
ENT.PivotSteerWheelRPM = 40
ENT.PivotSteerTorqueMul = 2
function ENT:GetPivotSteer()
return self._PivotSteer or 0
end
function ENT:SetPivotSteer( new )
self._PivotSteer = new
end
function ENT:PivotSteer()
if not self.PivotSteerEnable then return false end
return (self._PivotSteer or 0) ~= 0
end
function ENT:CalcPivotSteer( ply )
local KeyLeft = ply:lvsKeyDown( "CAR_STEER_LEFT" )
local KeyRight = ply:lvsKeyDown( "CAR_STEER_RIGHT" )
local KeyThrottle = ply:lvsKeyDown( "CAR_THROTTLE" )
local KeyBrake = ply:lvsKeyDown( "CAR_BRAKE" )
local ShouldSteer = (KeyLeft or KeyRight) and not KeyBrake and not KeyThrottle
local Throttle = self:GetThrottle()
if self._oldShouldSteer ~= ShouldSteer then
self._oldShouldSteer = ShouldSteer
if ShouldSteer then
self._ShouldSteer = true
end
end
if ShouldSteer then
self._PivotSteer = (KeyRight and 1 or 0) - (KeyLeft and 1 or 0)
self:SetSteer( 0 )
end
if not ShouldSteer and self._ShouldSteer then
self:LerpThrottle( 0 )
if Throttle <= 0 then
self._ShouldSteer = nil
self._PivotSteer = 0
end
return
end
if not self._ShouldSteer then return end
self:LerpThrottle( (KeyRight or KeyLeft) and 1 or 0 )
end

View File

@@ -0,0 +1,99 @@
local function SetAll( ent, n )
if not IsValid( ent ) then return end
ent:SetPoseParameter("vehicle_wheel_fl_height",n)
ent:SetPoseParameter("vehicle_wheel_fr_height",n)
ent:SetPoseParameter("vehicle_wheel_rl_height",n)
ent:SetPoseParameter("vehicle_wheel_rr_height",n)
end
function ENT:CreateRigControler( name, wheelEntity, min, max )
local RigHandler = ents.Create( "lvs_wheeldrive_righandler" )
if not IsValid( RigHandler ) then
self:Remove()
print("LVS: Failed to create righandler entity. Vehicle terminated.")
return
end
RigHandler:SetPos( self:GetPos() )
RigHandler:SetAngles( self:GetAngles() )
RigHandler:Spawn()
RigHandler:Activate()
RigHandler:SetParent( self )
RigHandler:SetBase( self )
RigHandler:SetPose0( min )
RigHandler:SetPose1( max )
RigHandler:SetWheel( wheelEntity )
RigHandler:SetNameID( name )
self:DeleteOnRemove( RigHandler )
self:TransferCPPI( RigHandler )
return RigHandler
end
function ENT:AddWheelsUsingRig( FrontRadius, RearRadius, data )
if not istable( data ) then data = {} end
local Body = ents.Create( "prop_dynamic" )
Body:SetModel( self:GetModel() )
Body:SetPos( self:GetPos() )
Body:SetAngles( self:GetAngles() )
Body:SetMoveType( MOVETYPE_NONE )
Body:Spawn()
Body:Activate()
Body:SetColor( Color(255,255,255,0) )
Body:SetRenderMode( RENDERMODE_TRANSCOLOR )
SetAll( Body, 0 )
SafeRemoveEntityDelayed( Body, 0.3 )
local id_fl = Body:LookupAttachment( "wheel_fl" )
local id_fr = Body:LookupAttachment( "wheel_fr" )
local id_rl = Body:LookupAttachment( "wheel_rl" )
local id_rr = Body:LookupAttachment( "wheel_rr" )
local ForwardAngle = angle_zero
if not isnumber( FrontRadius ) or not isnumber( RearRadius ) or id_fl == 0 or id_fr == 0 or id_rl == 0 or id_rr == 0 then return NULL, NULL, NULL, NULL, ForwardAngle end
local pFL0 = Body:WorldToLocal( Body:GetAttachment( id_fl ).Pos )
local pFR0 = Body:WorldToLocal( Body:GetAttachment( id_fr ).Pos )
local pRL0 = Body:WorldToLocal( Body:GetAttachment( id_rl ).Pos )
local pRR0 = Body:WorldToLocal( Body:GetAttachment( id_rr ).Pos )
local ForwardAngle = ((pFL0 + pFR0) / 2 - (pRL0 + pRR0) / 2):Angle()
ForwardAngle.p = 0
ForwardAngle.y = math.Round( ForwardAngle.y, 0 )
ForwardAngle.r = 0
ForwardAngle:Normalize()
local FL = self:AddWheel( { hide = (not isstring( data.mdl_fl )), pos = pFL0, radius = FrontRadius, mdl = data.mdl_fl, mdl_ang = data.mdl_ang_fl } )
local FR = self:AddWheel( { hide = (not isstring( data.mdl_fr )), pos = pFR0, radius = FrontRadius, mdl = data.mdl_fr, mdl_ang = data.mdl_ang_fr } )
local RL = self:AddWheel( { hide = (not isstring( data.mdl_rl )), pos = pRL0, radius = RearRadius, mdl = data.mdl_rl, mdl_ang = data.mdl_ang_rl } )
local RR = self:AddWheel( { hide = (not isstring( data.mdl_rr )), pos = pRR0, radius = RearRadius, mdl = data.mdl_rr, mdl_ang = data.mdl_ang_rr } )
SetAll( Body, 1 )
timer.Simple( 0.15, function()
if not IsValid( self ) or not IsValid( Body ) then return end
local pFL1 = Body:WorldToLocal( Body:GetAttachment( id_fl ).Pos )
local pFR1 = Body:WorldToLocal( Body:GetAttachment( id_fr ).Pos )
local pRL1 = Body:WorldToLocal( Body:GetAttachment( id_rl ).Pos )
local pRR1 = Body:WorldToLocal( Body:GetAttachment( id_rr ).Pos )
self:CreateRigControler( "fl", FL, pFL0.z, pFL1.z )
self:CreateRigControler( "fr", FR, pFR0.z, pFR1.z )
self:CreateRigControler( "rl", RL, pRL0.z, pRL1.z )
self:CreateRigControler( "rr", RR, pRR0.z, pRR1.z )
end )
return FL, FR, RL, RR, ForwardAngle
end

View File

@@ -0,0 +1,451 @@
ENT._WheelEnts = {}
ENT._WheelAxleID = 0
ENT._WheelAxleData = {}
function ENT:ClearWheels()
for _, ent in pairs( self:GetWheels() ) do
ent:Remove()
end
table.Empty( self._WheelEnts )
table.Empty( self._WheelAxleData )
self._WheelAxleID = 0
end
function ENT:GetWheels()
local EntTable = self:GetTable()
for id, ent in pairs( EntTable._WheelEnts ) do
if IsValid( ent ) then continue end
EntTable._WheelEnts[ id ] = nil
end
return EntTable._WheelEnts
end
function ENT:GetAxleData( ID )
local EntTable = self:GetTable()
if not EntTable._WheelAxleData[ ID ] then return {} end
return EntTable._WheelAxleData[ ID ]
end
function ENT:CreateSteerMaster( TargetEntity )
if not IsValid( TargetEntity ) then return end
local Master = ents.Create( "lvs_wheeldrive_steerhandler" )
if not IsValid( Master ) then
self:Remove()
print("LVS: Failed to create steermaster entity. Vehicle terminated.")
return
end
Master:SetPos( TargetEntity:GetPos() )
Master:SetAngles( Angle(0,90,0) )
Master:Spawn()
Master:Activate()
self:DeleteOnRemove( Master )
self:TransferCPPI( Master )
return Master
end
function ENT:AddWheel( data )
if not istable( data ) or not isvector( data.pos ) then return end
local Wheel = ents.Create( "lvs_wheeldrive_wheel" )
if not IsValid( Wheel ) then
self:Remove()
print("LVS: Failed to create wheel entity. Vehicle terminated.")
return
end
Wheel:SetModel( data.mdl or "models/props_vehicles/tire001c_car.mdl" )
Wheel:SetPos( self:LocalToWorld( data.pos ) )
Wheel:SetAngles( Angle(0,0,0) )
Wheel:Spawn()
Wheel:Activate()
Wheel:SetBase( self )
Wheel:SetAlignmentAngle( data.mdl_ang or Angle(0,0,0) )
Wheel:SetHideModel( data.hide == true )
Wheel:lvsMakeSpherical( data.radius or -1 )
Wheel:SetWidth( data.width or 4 )
Wheel:SetCamber( data.camber or 0 )
Wheel:SetCaster( data.caster or 0 )
Wheel:SetToe( data.toe or 0 )
Wheel:CheckAlignment()
Wheel:SetWheelType( data.wheeltype )
if isnumber( data.MaxHealth ) then
Wheel:SetMaxHP( data.MaxHealth )
Wheel:SetHP( data.MaxHealth )
end
if isnumber( data.DSArmorIgnoreForce ) then
Wheel.DSArmorIgnoreForce = data.DSArmorIgnoreForce
end
self:DeleteOnRemove( Wheel )
self:TransferCPPI( Wheel )
local PhysObj = Wheel:GetPhysicsObject()
if not IsValid( PhysObj ) then
self:Remove()
print("LVS: Failed to create wheel physics. Vehicle terminated.")
return
end
PhysObj:SetMass( self.WheelPhysicsMass * self.PhysicsWeightScale )
PhysObj:SetInertia( self.WheelPhysicsInertia * self.PhysicsWeightScale )
PhysObj:EnableDrag( false )
PhysObj:EnableMotion( false )
local nocollide_constraint = constraint.NoCollide(self,Wheel,0,0)
nocollide_constraint.DoNotDuplicate = true
debugoverlay.Line( self:GetPos(), self:LocalToWorld( data.pos ), 5, Color(150,150,150), true )
table.insert( self._WheelEnts, Wheel )
local Master = self:CreateSteerMaster( Wheel )
local Lock = 0.0001
local B1 = constraint.AdvBallsocket( Wheel,Master,0,0,vector_origin,vector_origin,0,0,-180,-Lock,-Lock,180,Lock,Lock,0,0,0,1,1)
B1.DoNotDuplicate = true
local B2 = constraint.AdvBallsocket( Master,Wheel,0,0,vector_origin,vector_origin,0,0,-180,Lock,Lock,180,-Lock,-Lock,0,0,0,1,1)
B2.DoNotDuplicate = true
local expectedMaxRPM = math.max( self.MaxVelocity, self.MaxVelocityReverse ) * 60 / math.pi / (Wheel:GetRadius() * 2)
if expectedMaxRPM > 800 then
local B3 = constraint.AdvBallsocket( Wheel,Master,0,0,vector_origin,vector_origin,0,0,-180,Lock,Lock,180,-Lock,-Lock,0,0,0,1,1)
B3.DoNotDuplicate = true
local B4 = constraint.AdvBallsocket( Master,Wheel,0,0,vector_origin,vector_origin,0,0,-180,-Lock,-Lock,180,Lock,Lock,0,0,0,1,1)
B4.DoNotDuplicate = true
end
if expectedMaxRPM > 2150 then
local possibleMaxVelocity = (2150 * (math.pi * (Wheel:GetRadius() * 2))) / 60
self.MaxVelocity = math.min( self.MaxVelocity, possibleMaxVelocity )
self.MaxVelocityReverse = math.min( self.MaxVelocityReverse, possibleMaxVelocity )
print("[LVS] - peripheral speed out of range! clamping!" )
end
Wheel:SetMaster( Master )
timer.Simple(0, function()
if not IsValid( self ) or not IsValid( Wheel ) or not IsValid( PhysObj ) then return end
Master:SetAngles( self:GetAngles() )
Wheel:SetAngles( self:LocalToWorldAngles( Angle(0,-90,0) ) )
self:AddToMotionController( PhysObj )
PhysObj:EnableMotion( true )
end )
if isnumber( self._WheelSkin ) then Wheel:SetSkin( self._WheelSkin ) end
if IsColor( self._WheelColor ) then Wheel:SetColor( self._WheelColor ) end
return Wheel
end
function ENT:DefineAxle( data )
if not istable( data ) then print("LVS: couldn't define axle: no axle data") return end
if not istable( data.Axle ) or not istable( data.Wheels ) or not istable( data.Suspension ) then print("LVS: couldn't define axle: no axle/wheel/suspension data") return end
self._WheelAxleID = self._WheelAxleID + 1
-- defaults
if self.ForcedForwardAngle then
data.Axle.ForwardAngle = self.ForcedForwardAngle
else
data.Axle.ForwardAngle = data.Axle.ForwardAngle or Angle(0,0,0)
end
data.Axle.SteerType = data.Axle.SteerType or LVS.WHEEL_STEER_NONE
data.Axle.SteerAngle = data.Axle.SteerAngle or 20
data.Axle.TorqueFactor = data.Axle.TorqueFactor or 1
data.Axle.BrakeFactor = data.Axle.BrakeFactor or 1
data.Axle.UseHandbrake = data.Axle.UseHandbrake == true
if not self.ForwardAngle then self.ForwardAngle = data.Axle.ForwardAngle end
data.Suspension.Height = data.Suspension.Height or 20
data.Suspension.MaxTravel = data.Suspension.MaxTravel or data.Suspension.Height
data.Suspension.ControlArmLength = data.Suspension.ControlArmLength or 25
data.Suspension.SpringConstant = data.Suspension.SpringConstant or 20000
data.Suspension.SpringDamping = data.Suspension.SpringDamping or 2000
data.Suspension.SpringRelativeDamping = data.Suspension.SpringRelativeDamping or 2000
local AxleCenter = Vector(0,0,0)
for _, Wheel in ipairs( data.Wheels ) do
if not IsEntity( Wheel ) then print("LVS: !ERROR!, given wheel is not a entity!") return end
AxleCenter = AxleCenter + Wheel:GetPos()
if not Wheel.SetAxle then continue end
Wheel:SetAxle( self._WheelAxleID )
end
AxleCenter = AxleCenter / #data.Wheels
debugoverlay.Text( AxleCenter, "Axle "..self._WheelAxleID.." Center ", 5, true )
debugoverlay.Cross( AxleCenter, 5, 5, Color( 255, 0, 0 ), true )
debugoverlay.Line( AxleCenter, AxleCenter + self:LocalToWorldAngles( data.Axle.ForwardAngle ):Forward() * 25, 5, Color(255,0,0), true )
debugoverlay.Text( AxleCenter + self:LocalToWorldAngles( data.Axle.ForwardAngle ):Forward() * 25, "Axle "..self._WheelAxleID.." Forward", 5, true )
data.Axle.CenterPos = self:WorldToLocal( AxleCenter )
self._WheelAxleData[ self._WheelAxleID ] = {
ForwardAngle = data.Axle.ForwardAngle,
AxleCenter = data.Axle.CenterPos,
SteerType = data.Axle.SteerType,
SteerAngle = data.Axle.SteerAngle,
TorqueFactor = data.Axle.TorqueFactor,
BrakeFactor = data.Axle.BrakeFactor,
UseHandbrake = data.Axle.UseHandbrake,
}
for id, Wheel in ipairs( data.Wheels ) do
local Elastic = self:CreateSuspension( Wheel, AxleCenter, self:LocalToWorldAngles( data.Axle.ForwardAngle ), data.Suspension )
Wheel.SuspensionConstraintElastic = Elastic
debugoverlay.Line( AxleCenter, Wheel:GetPos(), 5, Color(150,0,0), true )
debugoverlay.Text( Wheel:GetPos(), "Axle "..self._WheelAxleID.." Wheel "..id, 5, true )
local AngleStep = 15
for ang = 15, 360, AngleStep do
if not Wheel.GetRadius then continue end
local radius = Wheel:GetRadius()
local X1 = math.cos( math.rad( ang ) ) * radius
local Y1 = math.sin( math.rad( ang ) ) * radius
local X2 = math.cos( math.rad( ang + AngleStep ) ) * radius
local Y2 = math.sin( math.rad( ang + AngleStep ) ) * radius
local P1 = Wheel:GetPos() + self:LocalToWorldAngles( data.Axle.ForwardAngle ):Up() * Y1 + self:LocalToWorldAngles( data.Axle.ForwardAngle ):Forward() * X1
local P2 = Wheel:GetPos() + self:LocalToWorldAngles( data.Axle.ForwardAngle ):Up() * Y2 + self:LocalToWorldAngles( data.Axle.ForwardAngle ):Forward() * X2
debugoverlay.Line( P1, P2, 5, Color( 150, 150, 150 ), true )
end
-- nocollide them with each other
for i = id, #data.Wheels do
local Ent = data.Wheels[ i ]
if Ent == Wheel then continue end
local nocollide_constraint = constraint.NoCollide(Ent,Wheel,0,0)
nocollide_constraint.DoNotDuplicate = true
end
end
return self._WheelAxleData[ self._WheelAxleID ]
end
function ENT:CreateSuspension( Wheel, CenterPos, DirectionAngle, data )
if not IsValid( Wheel ) or not IsEntity( Wheel ) then return end
local height = data.Height
local maxtravel = data.MaxTravel
local constant = data.SpringConstant
local damping = data.SpringDamping
local rdamping = data.SpringRelativeDamping
local LimiterLength = 60
local LimiterRopeLength = math.sqrt( maxtravel ^ 2 + LimiterLength ^ 2 )
local Pos = Wheel:GetPos()
local PosL, _ = WorldToLocal( Pos, DirectionAngle, CenterPos, DirectionAngle )
local Forward = DirectionAngle:Forward()
local Right = DirectionAngle:Right() * (PosL.y > 0 and 1 or -1)
local Up = DirectionAngle:Up()
local RopeSize = 0
local RopeLength = data.ControlArmLength
if height == 0 or maxtravel == 0 or RopeLength == 0 then
local ballsocket = constraint.Ballsocket( self, Wheel, 0, 0, Vector(0,0,0), 0, 0, 1 )
ballsocket.DoNotDuplicate = true
return
end
local P1 = Pos + Forward * RopeLength * 0.5 + Right * RopeLength
local P2 = Pos
local Rope1 = constraint.Rope(self, Wheel,0,0,self:WorldToLocal( P1 ), Vector(0,0,0), Vector(RopeLength * 0.5,RopeLength,0):Length(), 0, 0, RopeSize,"cable/cable2", true )
Rope1.DoNotDuplicate = true
debugoverlay.Line( P1, P2, 5, Color(0,255,0), true )
P1 = Pos - Forward * RopeLength * 0.5 + Right * RopeLength
local Rope2 = constraint.Rope(self, Wheel,0,0,self:WorldToLocal( P1 ), Vector(0,0,0), Vector(RopeLength * 0.5,RopeLength,0):Length(), 0, 0, RopeSize,"cable/cable2", true )
Rope2.DoNotDuplicate = true
debugoverlay.Line( P1, P2, 5, Color(0,255,0), true )
local Offset = Up * height
Wheel:SetPos( Pos - Offset )
local Limiter = constraint.Rope(self,Wheel,0,0,self:WorldToLocal( Pos - Up * height * 0.5 - Right * LimiterLength), Vector(0,0,0),LimiterRopeLength, 0, 0, RopeSize,"cable/cable2", false )
Limiter.DoNotDuplicate = true
P1 = Wheel:GetPos() + Up * (height * 2 + maxtravel * 2)
local Elastic = constraint.Elastic( Wheel, self, 0, 0, Vector(0,0,0), self:WorldToLocal( P1 ), constant, damping, rdamping,"cable/cable2", RopeSize, false )
Elastic.DoNotDuplicate = true
debugoverlay.SweptBox( P1, P2,- Vector(0,1,1), Vector(0,1,1), (P1 - P2):Angle(), 5, Color( 255, 255, 0 ), true )
Wheel:SetPos( Pos )
return Elastic
end
function ENT:AlignWheel( Wheel )
if not IsValid( Wheel ) then return false end
local Master = Wheel.MasterEntity
if not IsValid( Master ) then
if not isfunction( Wheel.GetMaster ) then return false end
Master = Wheel:GetMaster()
Wheel.MasterEntity = Master
if IsValid( Master ) then
Wheel.MasterPhysObj = Master:GetPhysicsObject()
end
return false
end
local PhysObj = Wheel.MasterPhysObj
if not IsValid( PhysObj ) then Wheel:Remove() return false end
local Steer = self:GetSteer()
if PhysObj:IsMotionEnabled() then PhysObj:EnableMotion( false ) return false end
if not Master.lvsValidAxleData then
local ID = Wheel:GetAxle()
if ID then
local Axle = self:GetAxleData( ID )
Master.AxleCenter = Axle.AxleCenter
Master.ForwardAngle = Axle.ForwardAngle or angle_zero
Master.SteerAngle = Axle.SteerAngle or 0
Master.SteerType = Axle.SteerType or LVS.WHEEL_STEER_NONE
Master.lvsValidAxleData = true
if Axle.SteerType == LVS.WHEEL_STEER_ACKERMANN then
if not self._AckermannCenter then
local AxleCenter = vector_origin
local NumAxles = 0
for _, data in pairs( self._WheelAxleData ) do
if data.SteerType and data.SteerType ~= LVS.WHEEL_STEER_NONE then continue end
AxleCenter = AxleCenter + data.AxleCenter
NumAxles = NumAxles + 1
end
self._AckermannCenter = AxleCenter / NumAxles
end
local Dist = (self._AckermannCenter - Axle.AxleCenter):Length()
if not self._AckermannDist or Dist > self._AckermannDist then
self._AckermannDist = Dist
end
end
end
return false
end
local AxleAng = self:LocalToWorldAngles( Master.ForwardAngle )
if Wheel.CamberCasterToe then
AxleAng:RotateAroundAxis( AxleAng:Right(), Wheel:GetCaster() )
AxleAng:RotateAroundAxis( AxleAng:Forward(), Wheel:GetCamber() )
AxleAng:RotateAroundAxis( AxleAng:Up(), Wheel:GetToe() )
end
local SteerType = Master.SteerType
if SteerType <= LVS.WHEEL_STEER_NONE then Master:SetAngles( AxleAng ) return true end
if SteerType == LVS.WHEEL_STEER_ACKERMANN then
if Steer ~= 0 then
local EntTable = self:GetTable()
local AxleCenter = self:LocalToWorld( Master.AxleCenter )
local RotCenter = self:LocalToWorld( EntTable._AckermannCenter )
local RotPoint = self:LocalToWorld( EntTable._AckermannCenter + Master.ForwardAngle:Right() * (EntTable._AckermannDist / math.tan( math.rad( Steer ) )) )
local A = (AxleCenter - RotCenter):Length()
local C = (Wheel:GetPos() - RotPoint):Length()
local Invert = ((self:VectorSplitNormal( Master.ForwardAngle:Forward(), Master.AxleCenter - EntTable._AckermannCenter ) < 0) and -1 or 1) * self:Sign( -Steer )
local Ang = (90 - math.deg( math.acos( A / C ) )) * Invert
AxleAng:RotateAroundAxis( AxleAng:Up(), Ang )
end
else
AxleAng:RotateAroundAxis( AxleAng:Up(), math.Clamp((SteerType == LVS.WHEEL_STEER_FRONT) and -Steer or Steer,-Master.SteerAngle,Master.SteerAngle) )
end
Master:SetAngles( AxleAng )
return true
end
function ENT:WheelsOnGround()
for _, ent in pairs( self:GetWheels() ) do
if not IsValid( ent ) then continue end
if ent:PhysicsOnGround() then
return true
end
end
return false
end
function ENT:OnWheelCollision( data, physobj )
end