add sborka
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user