add sborka
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
|
||||
function ENT:StopWheelEffects()
|
||||
if not self._DoingWheelFx then return end
|
||||
|
||||
self._DoingWheelFx = nil
|
||||
|
||||
self:FinishSkidmark()
|
||||
end
|
||||
|
||||
function ENT:StartWheelEffects( Base, trace, traceWater )
|
||||
self:DoWheelEffects( Base, trace, traceWater )
|
||||
|
||||
if self._DoingWheelFx then return end
|
||||
|
||||
self._DoingWheelFx = true
|
||||
end
|
||||
|
||||
function ENT:DoWheelEffects( Base, trace, traceWater )
|
||||
if not trace.Hit then self:FinishSkidmark() return end
|
||||
|
||||
local SurfacePropName = util.GetSurfacePropName( trace.SurfaceProps )
|
||||
local SkidValue = self:GetSkid()
|
||||
|
||||
if traceWater.Hit then
|
||||
local Scale = math.min( 0.3 + (SkidValue - 100) / 4000, 1 ) ^ 2
|
||||
|
||||
local effectdata = EffectData()
|
||||
effectdata:SetOrigin( trace.HitPos )
|
||||
effectdata:SetEntity( Base )
|
||||
effectdata:SetNormal( trace.HitNormal )
|
||||
effectdata:SetMagnitude( Scale )
|
||||
effectdata:SetFlags( 1 )
|
||||
util.Effect( "lvs_physics_wheeldust", effectdata, true, true )
|
||||
|
||||
self:FinishSkidmark()
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if self.SkidmarkSurfaces[ SurfacePropName ] then
|
||||
local Scale = math.min( 0.3 + SkidValue / 4000, 1 ) ^ 2
|
||||
|
||||
if Scale > 0.2 then
|
||||
self:StartSkidmark( trace.HitPos )
|
||||
self:CalcSkidmark( trace, Base:GetCrosshairFilterEnts() )
|
||||
else
|
||||
self:FinishSkidmark()
|
||||
end
|
||||
|
||||
local effectdata = EffectData()
|
||||
effectdata:SetOrigin( trace.HitPos )
|
||||
effectdata:SetEntity( Base )
|
||||
effectdata:SetNormal( trace.HitNormal )
|
||||
util.Effect( "lvs_physics_wheelsmoke", effectdata, true, true )
|
||||
else
|
||||
self:FinishSkidmark()
|
||||
end
|
||||
|
||||
if not LVS.ShowEffects then return end
|
||||
|
||||
if self.DustEffectSurfaces[ SurfacePropName ] then
|
||||
local Scale = math.min( 0.3 + (SkidValue - 100) / 4000, 1 ) ^ 2
|
||||
|
||||
local effectdata = EffectData()
|
||||
effectdata:SetOrigin( trace.HitPos )
|
||||
effectdata:SetEntity( Base )
|
||||
effectdata:SetNormal( trace.HitNormal )
|
||||
effectdata:SetMagnitude( Scale )
|
||||
effectdata:SetFlags( 0 )
|
||||
util.Effect( "lvs_physics_wheeldust", effectdata, true, true )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:DoWaterEffects( Base, traceWater, Pos )
|
||||
local effectdata = EffectData()
|
||||
effectdata:SetOrigin( traceWater.Fraction > 0.5 and traceWater.HitPos or Pos )
|
||||
effectdata:SetEntity( Base )
|
||||
effectdata:SetMagnitude( self:GetRadius() )
|
||||
effectdata:SetFlags( 0 )
|
||||
util.Effect( "lvs_physics_wheelwatersplash", effectdata )
|
||||
end
|
||||
|
||||
function ENT:DoWheelChainEffects( Base, trace )
|
||||
if not LVS.ShowEffects then return end
|
||||
|
||||
if not self.DustEffectSurfaces[ util.GetSurfacePropName( trace.SurfaceProps ) ] then return end
|
||||
|
||||
local effectdata = EffectData()
|
||||
effectdata:SetOrigin( trace.HitPos )
|
||||
effectdata:SetEntity( Base )
|
||||
effectdata:SetMagnitude( self:GetRadius() )
|
||||
effectdata:SetNormal( trace.HitNormal )
|
||||
util.Effect( "lvs_physics_trackdust", effectdata, true, true )
|
||||
end
|
||||
|
||||
function ENT:CalcWheelEffects()
|
||||
local Base = self:GetBase()
|
||||
|
||||
if not IsValid( Base ) then return end
|
||||
|
||||
local T = CurTime()
|
||||
local EntTable = self:GetTable()
|
||||
|
||||
if (EntTable._NextWheelSound or 0) < T then
|
||||
EntTable._NextWheelSound = T + 0.05
|
||||
|
||||
if EntTable._fxDelay ~= 1 and EntTable.TraceResult and EntTable.TraceResultWater then
|
||||
self:CalcWheelSounds( Base, EntTable.TraceResult, EntTable.TraceResultWater )
|
||||
end
|
||||
end
|
||||
|
||||
if (EntTable._NextFx or 0) > T then return end
|
||||
|
||||
local ply = LocalPlayer()
|
||||
|
||||
if not IsValid( ply ) then return end
|
||||
|
||||
local ViewEnt = ply:GetViewEntity()
|
||||
if IsValid( ViewEnt ) then
|
||||
ply = ViewEnt
|
||||
end
|
||||
|
||||
local Delay = 0.05
|
||||
|
||||
if self:GetWidth() <= 0 then
|
||||
EntTable._fxDelay = math.min( Delay + (self:GetPos() - ply:GetPos()):LengthSqr() * 0.00000005, 1 )
|
||||
else
|
||||
EntTable._fxDelay = math.min( Delay + (self:GetPos() - ply:GetPos()):LengthSqr() * 0.000000001, 1 )
|
||||
end
|
||||
|
||||
EntTable._NextFx = T + EntTable._fxDelay
|
||||
|
||||
local Radius = Base:GetWheelUp() * (self:GetRadius() + 1)
|
||||
|
||||
local Vel = self:GetVelocity()
|
||||
local Pos = self:GetPos() + Vel * 0.025
|
||||
|
||||
local StartPos = Pos + Radius
|
||||
local EndPos = Pos - Radius
|
||||
|
||||
local traceData = {
|
||||
start = StartPos,
|
||||
endpos = EndPos,
|
||||
filter = Base:GetCrosshairFilterEnts(),
|
||||
}
|
||||
|
||||
local trace = util.TraceLine( traceData )
|
||||
|
||||
traceData.mask = MASK_WATER
|
||||
|
||||
local traceWater = util.TraceLine( traceData )
|
||||
|
||||
EntTable.TraceResult = trace
|
||||
EntTable.TraceResultWater = traceWater
|
||||
|
||||
if traceWater.Hit and trace.HitPos.z < traceWater.HitPos.z then
|
||||
if math.abs( self:GetRPM() ) > 25 then
|
||||
self:DoWaterEffects( Base, traceWater, Pos )
|
||||
end
|
||||
else
|
||||
if self:GetWheelChainMode() and trace.Hit and math.abs( self:GetRPM() ) > 25 and Vel:LengthSqr() > 1500 then
|
||||
self:DoWheelChainEffects( Base, trace )
|
||||
end
|
||||
end
|
||||
|
||||
if self:GetSlip() < 500 or EntTable._fxDelay > 0.1 then self:StopWheelEffects() return end
|
||||
|
||||
self:StartWheelEffects( Base, trace, traceWater )
|
||||
end
|
||||
|
||||
function ENT:CalcWheelSounds( Base, trace, traceWater )
|
||||
if not trace.Hit then return end
|
||||
|
||||
local RPM = math.abs( self:GetRPM() )
|
||||
|
||||
if self:GetDamaged() and RPM > 30 then
|
||||
if self:GetWheelChainMode() then
|
||||
local effectdata = EffectData()
|
||||
effectdata:SetOrigin( self:GetPos() )
|
||||
effectdata:SetNormal( self:GetForward() )
|
||||
util.Effect( "lvs_physics_trackscraping", effectdata, true, true )
|
||||
|
||||
Base:DoTireSound( "tracks_damage_layer" )
|
||||
else
|
||||
local Ang = self:GetForward():Angle() + Angle(10,0,0)
|
||||
Ang:RotateAroundAxis( Base:GetUp(), -90 )
|
||||
|
||||
local effectdata = EffectData()
|
||||
effectdata:SetOrigin( trace.HitPos + trace.HitNormal )
|
||||
effectdata:SetNormal( Ang:Forward() * math.min( self:GetSlip() / 10000, 3 ) * (self:GetRPM() > 0 and 1 or -1) )
|
||||
effectdata:SetMagnitude( 1 )
|
||||
util.Effect( "manhacksparks", effectdata, true, true )
|
||||
|
||||
Base:DoTireSound( "tire_damage_layer" )
|
||||
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if not Base:GetEngineActive() and RPM < 50 then return end
|
||||
|
||||
if traceWater.Hit then
|
||||
Base:DoTireSound( "roll_wet" )
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local surface = self.DustEffectSurfaces[ util.GetSurfacePropName( trace.SurfaceProps ) ] and "_dirt" or ""
|
||||
local snd_type = (self:GetSlip() > 500) and "skid" or "roll"
|
||||
|
||||
if Base:GetRacingTires() and surface == "" then surface = "_racing" end
|
||||
|
||||
if (istable( StormFox ) or istable( StormFox2 )) and surface ~= "_dirt" then
|
||||
local Rain = false
|
||||
|
||||
if StormFox then
|
||||
Rain = StormFox.IsRaining()
|
||||
end
|
||||
|
||||
if StormFox2 then
|
||||
Rain = StormFox2.Weather:IsRaining()
|
||||
end
|
||||
|
||||
if Rain then
|
||||
local effectdata = EffectData()
|
||||
effectdata:SetOrigin( trace.HitPos )
|
||||
effectdata:SetEntity( Base )
|
||||
effectdata:SetMagnitude( self:BoundingRadius() )
|
||||
effectdata:SetFlags( 1 )
|
||||
util.Effect( "lvs_physics_wheelwatersplash", effectdata )
|
||||
|
||||
Base:DoTireSound( snd_type.."_wet" )
|
||||
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if snd_type == "roll" and not self:GetWheelChainMode() and self:GetHP() ~= self:GetMaxHP() then
|
||||
surface = "_damaged"
|
||||
end
|
||||
|
||||
Base:DoTireSound( snd_type..surface )
|
||||
end
|
||||
@@ -0,0 +1,117 @@
|
||||
include("shared.lua")
|
||||
include("cl_effects.lua")
|
||||
include("cl_skidmarks.lua")
|
||||
|
||||
function ENT:Initialize()
|
||||
local Mins, Maxs = self:GetRenderBounds()
|
||||
|
||||
self:SetRenderBounds( Mins, Maxs, Vector( 50, 50, 50 ) )
|
||||
|
||||
self:DrawShadow( false )
|
||||
end
|
||||
|
||||
if GravHull then
|
||||
function ENT:DrawWheel( flags )
|
||||
self:SetAngles( self:LocalToWorldAngles( self:GetAlignmentAngle() ) ) -- GravHull overwrites SetRenderAngles, but SetAngles works too...
|
||||
|
||||
self:DrawModel( flags )
|
||||
end
|
||||
else
|
||||
function ENT:DrawWheel( flags )
|
||||
self:SetRenderAngles( self:LocalToWorldAngles( self:GetAlignmentAngle() ) )
|
||||
|
||||
self:DrawModel( flags )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:DrawWheelBroken( flags )
|
||||
local base = self:GetBase()
|
||||
|
||||
if not IsValid( base ) or not LVS.MapDoneLoading then
|
||||
self:DrawModel( flags )
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
-- Alternative method, tuning wheels... Workaround for diggers wheel pack. Flickers for some people... it is what it is
|
||||
if self:GetBoneCount() > 1 then
|
||||
local pos = self:GetPos()
|
||||
|
||||
self:SetRenderOrigin( pos - base:GetUp() * base.WheelPhysicsTireHeight )
|
||||
self:DrawWheel( flags )
|
||||
self:SetRenderOrigin()
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
-- bone position method... more reliable and works on infmap, but doesnt work on diggers wheel pack
|
||||
|
||||
self:SetupBones()
|
||||
|
||||
local pos, ang = self:GetBonePosition( 0 )
|
||||
|
||||
if not pos then self:DrawModel( flags ) return end
|
||||
|
||||
self:SetBonePosition( 0, pos - base:GetUp() * base.WheelPhysicsTireHeight, ang )
|
||||
|
||||
self:DrawWheel( flags )
|
||||
|
||||
self:SetBonePosition( 0, pos , ang )
|
||||
end
|
||||
|
||||
function ENT:Draw( flags )
|
||||
if self:GetHideModel() then return end
|
||||
|
||||
if self:GetNWDamaged() then
|
||||
|
||||
self:DrawWheelBroken( flags )
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
self:DrawWheel( flags )
|
||||
end
|
||||
|
||||
function ENT:DrawTranslucent()
|
||||
self:CalcWheelEffects()
|
||||
end
|
||||
|
||||
function ENT:Think()
|
||||
self:CalcWheelSlip()
|
||||
|
||||
self:SetNextClientThink( CurTime() + 0.1 )
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function ENT:OnRemove()
|
||||
self:StopWheelEffects()
|
||||
end
|
||||
|
||||
function ENT:CalcWheelSlip()
|
||||
local Base = self:GetBase()
|
||||
|
||||
if not IsValid( Base ) then return end
|
||||
|
||||
local Vel = self:GetVelocity()
|
||||
local VelLength = Vel:Length()
|
||||
|
||||
local rpmTheoretical = self:VelToRPM( VelLength )
|
||||
local rpm = math.abs( self:GetRPM() )
|
||||
|
||||
if rpm == 0 then
|
||||
self._WheelSlip = rpmTheoretical + VelLength * 4
|
||||
self._WheelSkid = self._WheelSlip
|
||||
else
|
||||
self._WheelSlip = math.max( rpm - rpmTheoretical - 10, 0 ) ^ 2 + math.max( math.abs( Base:VectorSplitNormal( self:GetForward(), Vel * 4 ) ) - VelLength, 0 )
|
||||
self._WheelSkid = VelLength + self._WheelSlip
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:GetSlip()
|
||||
return (self._WheelSlip or 0)
|
||||
end
|
||||
|
||||
function ENT:GetSkid()
|
||||
return (self._WheelSkid or 0)
|
||||
end
|
||||
@@ -0,0 +1,182 @@
|
||||
|
||||
ENT.SkidmarkMaterial = Material("sprites/lvs/skidmark")
|
||||
ENT.SkidmarkMaterialDamaged = Material("sprites/lvs/skidmark_damaged")
|
||||
|
||||
ENT.SkidmarkTraceAdd = Vector(0,0,10)
|
||||
ENT.SkidmarkDelay = 0.05
|
||||
ENT.SkidmarkLifetime = 10
|
||||
|
||||
ENT.SkidmarkRed = 0
|
||||
ENT.SkidmarkGreen = 0
|
||||
ENT.SkidmarkBlue = 0
|
||||
ENT.SkidmarkAlpha = 150
|
||||
|
||||
ENT.SkidmarkSurfaces = {
|
||||
[""] = true,
|
||||
["concrete"] = true,
|
||||
["plastic_barrel_buoyant"] = true,
|
||||
["phx_rubbertire2"] = true,
|
||||
["tile"] = true,
|
||||
["metal"] = true,
|
||||
["boulder"] = true,
|
||||
["default"] = true,
|
||||
}
|
||||
|
||||
ENT.DustEffectSurfaces = {
|
||||
["sand"] = true,
|
||||
["dirt"] = true,
|
||||
["grass"] = true,
|
||||
["antlionsand"] = true,
|
||||
["gravel"] = true,
|
||||
}
|
||||
|
||||
function ENT:GetSkidMarks()
|
||||
if not istable( self._activeSkidMarks ) then
|
||||
self._activeSkidMarks = {}
|
||||
end
|
||||
|
||||
return self._activeSkidMarks
|
||||
end
|
||||
|
||||
function ENT:StartSkidmark( pos )
|
||||
if self:GetWidth() <= 0 or self._SkidMarkID or not LVS.ShowTraileffects then return end
|
||||
|
||||
local ID = 1
|
||||
for _,_ in ipairs( self:GetSkidMarks() ) do
|
||||
ID = ID + 1
|
||||
end
|
||||
|
||||
self._activeSkidMarks[ ID ] = {
|
||||
active = true,
|
||||
startpos = pos + self.SkidmarkTraceAdd,
|
||||
delay = CurTime() + self.SkidmarkDelay,
|
||||
damaged = self:GetNWDamaged(),
|
||||
positions = {},
|
||||
}
|
||||
|
||||
self._SkidMarkID = ID
|
||||
end
|
||||
|
||||
function ENT:FinishSkidmark()
|
||||
if not self._SkidMarkID then return end
|
||||
|
||||
self._activeSkidMarks[ self._SkidMarkID ].active = false
|
||||
|
||||
self._SkidMarkID = nil
|
||||
end
|
||||
|
||||
function ENT:RemoveSkidmark( id )
|
||||
if not id then return end
|
||||
|
||||
self._activeSkidMarks[ id ] = nil
|
||||
end
|
||||
|
||||
function ENT:CalcSkidmark( trace, Filter )
|
||||
local T = CurTime()
|
||||
local CurActive = self:GetSkidMarks()[ self._SkidMarkID ]
|
||||
|
||||
if not CurActive or not CurActive.active or CurActive.delay >= T then return end
|
||||
|
||||
CurActive.delay = T + self.SkidmarkDelay
|
||||
|
||||
local W = self:GetWidth()
|
||||
|
||||
local cur = trace.HitPos + self.SkidmarkTraceAdd * 0.5
|
||||
|
||||
local prev = CurActive.positions[ #CurActive.positions ]
|
||||
|
||||
if not prev then
|
||||
local sub = cur - CurActive.startpos
|
||||
|
||||
local L = sub:Length() * 0.5
|
||||
local C = (cur + CurActive.startpos) * 0.5
|
||||
|
||||
local Ang = sub:Angle()
|
||||
local Forward = Ang:Right()
|
||||
local Right = Ang:Forward()
|
||||
|
||||
local p1 = C + Forward * W + Right * L
|
||||
local p2 = C - Forward * W + Right * L
|
||||
|
||||
local t1 = util.TraceLine( { start = p1, endpos = p1 - self.SkidmarkTraceAdd } )
|
||||
local t2 = util.TraceLine( { start = p2, endpos = p2 - self.SkidmarkTraceAdd } )
|
||||
|
||||
prev = {
|
||||
px = CurActive.startpos,
|
||||
p1 = t1.HitPos + t1.HitNormal,
|
||||
p2 = t2.HitPos + t2.HitNormal,
|
||||
lifetime = T + self.SkidmarkLifetime - self.SkidmarkDelay,
|
||||
alpha = 0,
|
||||
}
|
||||
end
|
||||
|
||||
local sub = cur - prev.px
|
||||
|
||||
local L = sub:Length() * 0.5
|
||||
local C = (cur + prev.px) * 0.5
|
||||
|
||||
local Ang = sub:Angle()
|
||||
local Forward = Ang:Right()
|
||||
local Right = Ang:Forward()
|
||||
|
||||
local p1 = C + Forward * W + Right * L
|
||||
local p2 = C - Forward * W + Right * L
|
||||
|
||||
local t1 = util.TraceLine( { start = p1, endpos = p1 - self.SkidmarkTraceAdd, filter = Filter, } )
|
||||
local t2 = util.TraceLine( { start = p2, endpos = p2 - self.SkidmarkTraceAdd, filter = Filter, } )
|
||||
|
||||
local nextID = #CurActive.positions + 1
|
||||
|
||||
CurActive.positions[ nextID ] = {
|
||||
px = cur,
|
||||
p1 = t1.HitPos + t1.HitNormal,
|
||||
p2 = t2.HitPos + t2.HitNormal,
|
||||
lifetime = T + self.SkidmarkLifetime,
|
||||
alpha = math.min( nextID / 10, 1 ),
|
||||
}
|
||||
end
|
||||
|
||||
function ENT:RenderSkidMarks()
|
||||
local T = CurTime()
|
||||
|
||||
for id, skidmark in pairs( self:GetSkidMarks() ) do
|
||||
local prev
|
||||
local AmountDrawn = 0
|
||||
|
||||
if skidmark.damaged then
|
||||
render.SetMaterial( self.SkidmarkMaterialDamaged )
|
||||
else
|
||||
render.SetMaterial( self.SkidmarkMaterial )
|
||||
end
|
||||
|
||||
for markID, data in pairs( skidmark.positions ) do
|
||||
if not prev then
|
||||
|
||||
prev = data
|
||||
|
||||
continue
|
||||
end
|
||||
|
||||
local Mul = math.max( data.lifetime - CurTime(), 0 ) / self.SkidmarkLifetime
|
||||
|
||||
if Mul > 0 then
|
||||
AmountDrawn = AmountDrawn + 1
|
||||
render.DrawQuad( data.p2, data.p1, prev.p1, prev.p2, Color( self.SkidmarkRed, self.SkidmarkGreen, self.SkidmarkBlue, math.min(255 * Mul * data.alpha,self.SkidmarkAlpha) ) )
|
||||
end
|
||||
|
||||
prev = data
|
||||
end
|
||||
|
||||
if not skidmark.active and AmountDrawn == 0 then
|
||||
self:RemoveSkidmark( id )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
hook.Add( "PreDrawTranslucentRenderables", "!!!!lvs_skidmarks", function( bDepth, bSkybox )
|
||||
if bSkybox then return end
|
||||
|
||||
for _, wheel in ipairs( ents.FindByClass("lvs_wheeldrive_wheel") ) do
|
||||
wheel:RenderSkidMarks()
|
||||
end
|
||||
end)
|
||||
@@ -0,0 +1,150 @@
|
||||
AddCSLuaFile( "shared.lua" )
|
||||
AddCSLuaFile( "cl_init.lua" )
|
||||
AddCSLuaFile( "cl_effects.lua" )
|
||||
AddCSLuaFile( "cl_skidmarks.lua" )
|
||||
include("shared.lua")
|
||||
include("sv_axle.lua")
|
||||
include("sv_brakes.lua")
|
||||
include("sv_damage.lua")
|
||||
|
||||
function ENT:GetWheelType()
|
||||
return self._WheelType or LVS.WHEELTYPE_NONE
|
||||
end
|
||||
|
||||
function ENT:SetWheelType( wheel_type )
|
||||
self._WheelType = wheel_type
|
||||
end
|
||||
|
||||
function ENT:SetSuspensionHeight( newheight )
|
||||
newheight = newheight and math.Clamp( newheight, -1, 1 ) or 0
|
||||
|
||||
self._SuspensionHeightMultiplier = newheight
|
||||
|
||||
if not IsValid( self.SuspensionConstraintElastic ) then return end
|
||||
|
||||
local Length = self.SuspensionConstraintElastic:GetTable().length or 25
|
||||
|
||||
self.SuspensionConstraintElastic:Fire( "SetSpringLength", Length + Length * newheight )
|
||||
end
|
||||
|
||||
function ENT:GetSuspensionHeight()
|
||||
return self._SuspensionHeightMultiplier or 0
|
||||
end
|
||||
|
||||
function ENT:SetSuspensionStiffness( new )
|
||||
new = new and math.Clamp( new, -1, 1 ) or 0
|
||||
|
||||
self._SuspensionStiffnessMultiplier = new
|
||||
|
||||
if not IsValid( self.SuspensionConstraintElastic ) then return end
|
||||
|
||||
local data = self.SuspensionConstraintElastic:GetTable()
|
||||
local damping = data.damping or 2000
|
||||
local constant = data.constant or 20000
|
||||
|
||||
self.SuspensionConstraintElastic:Fire( "SetSpringConstant", constant + constant * new, 0 )
|
||||
self.SuspensionConstraintElastic:Fire( "SetSpringDamping", damping + damping * new, 0 )
|
||||
end
|
||||
|
||||
function ENT:GetSuspensionStiffness()
|
||||
return self._SuspensionStiffnessMultiplier or 0
|
||||
end
|
||||
|
||||
function ENT:Initialize()
|
||||
self:SetCollisionGroup( COLLISION_GROUP_PASSABLE_DOOR )
|
||||
|
||||
-- this is so vj npcs can still see us
|
||||
self:AddEFlags( EFL_DONTBLOCKLOS )
|
||||
end
|
||||
|
||||
function ENT:GravGunPickupAllowed( ply )
|
||||
return false
|
||||
end
|
||||
|
||||
function ENT:StartThink()
|
||||
if self.AutomaticFrameAdvance then return end
|
||||
|
||||
self.AutomaticFrameAdvance = true
|
||||
|
||||
self.Think = function( self )
|
||||
self:NextThink( CurTime() )
|
||||
return true
|
||||
end
|
||||
|
||||
self:Think()
|
||||
end
|
||||
|
||||
function ENT:Think()
|
||||
return false
|
||||
end
|
||||
|
||||
function ENT:OnRemove()
|
||||
self:StopLeakAir()
|
||||
end
|
||||
|
||||
function ENT:lvsMakeSpherical( radius )
|
||||
if not radius or radius <= 0 then
|
||||
radius = (self:OBBMaxs() - self:OBBMins()) * 0.5
|
||||
radius = math.max( radius.x, radius.y, radius.z )
|
||||
end
|
||||
|
||||
self:PhysicsInitSphere( radius, "jeeptire" )
|
||||
|
||||
self:SetRadius( radius )
|
||||
|
||||
self:DrawShadow( not self:GetHideModel() )
|
||||
end
|
||||
|
||||
function ENT:PhysicsMaterialUpdate( TargetValue )
|
||||
local base = self:GetBase()
|
||||
local PhysObj = self:GetPhysicsObject()
|
||||
|
||||
if not IsValid( base ) or not IsValid( PhysObj ) then return end
|
||||
|
||||
local ListID = math.Clamp( math.Round( (TargetValue or 1) * 10, 0 ), 0, 12 )
|
||||
|
||||
PhysObj:SetMaterial( base.WheelPhysicsMaterials[ ListID ] )
|
||||
end
|
||||
|
||||
function ENT:PhysicsOnGround()
|
||||
|
||||
local PhysObj = self:GetPhysicsObject()
|
||||
|
||||
if not IsValid( PhysObj ) then return false end
|
||||
|
||||
local EntLoad,_ = PhysObj:GetStress()
|
||||
|
||||
return EntLoad > 0
|
||||
end
|
||||
|
||||
function ENT:PhysicsCollide( data, physobj )
|
||||
|
||||
if data.Speed > 150 and data.DeltaTime > 0.2 then
|
||||
local VelDif = data.OurOldVelocity:Length() - data.OurNewVelocity:Length()
|
||||
|
||||
local Volume = math.min( math.abs( VelDif ) / 300 , 1 )
|
||||
|
||||
self:EmitSound( "lvs/vehicles/generic/suspension_hit_".. math.random(1,17) ..".ogg", 70, 100, Volume ^ 2 )
|
||||
end
|
||||
|
||||
local HitEntity = data.HitEntity
|
||||
local HitObject = data.HitObject
|
||||
|
||||
if IsValid( HitEntity ) and IsValid( HitObject ) then
|
||||
physobj:SetVelocityInstantaneous( data.OurOldVelocity )
|
||||
|
||||
if HitObject:IsMotionEnabled() and HitObject:GetMass() < physobj:GetMass() then
|
||||
HitObject:SetVelocityInstantaneous( data.OurOldVelocity * 2 )
|
||||
end
|
||||
end
|
||||
|
||||
if math.abs(data.OurNewVelocity.z - data.OurOldVelocity.z) > 100 then
|
||||
physobj:SetVelocityInstantaneous( data.OurOldVelocity )
|
||||
end
|
||||
|
||||
local base = self:GetBase()
|
||||
|
||||
if IsValid( base ) then
|
||||
base:OnWheelCollision( data, physobj )
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,84 @@
|
||||
|
||||
ENT.PrintName = "Wheel"
|
||||
|
||||
ENT.Type = "anim"
|
||||
ENT.DoNotDuplicate = true
|
||||
|
||||
ENT.RenderGroup = RENDERGROUP_BOTH
|
||||
|
||||
ENT._lvsRepairToolLabel = "Wheel"
|
||||
ENT._lvsNoPhysgunInteraction = true
|
||||
|
||||
function ENT:SetupDataTables()
|
||||
self:NetworkVar( "Float", 0, "Radius")
|
||||
self:NetworkVar( "Float", 1, "Width")
|
||||
|
||||
self:NetworkVar( "Float", 2, "Camber" )
|
||||
self:NetworkVar( "Float", 3, "Caster" )
|
||||
self:NetworkVar( "Float", 4, "Toe" )
|
||||
|
||||
self:NetworkVar( "Float", 5, "RPM" )
|
||||
|
||||
self:NetworkVar( "Float", 6, "HP" )
|
||||
self:NetworkVar( "Float", 7, "MaxHP" )
|
||||
|
||||
self:NetworkVar( "Angle", 0, "AlignmentAngle" )
|
||||
|
||||
self:NetworkVar( "Entity", 0, "Base" )
|
||||
|
||||
self:NetworkVar( "Bool", 0, "HideModel" )
|
||||
self:NetworkVar( "Bool", 1, "Destroyed" )
|
||||
self:NetworkVar( "Bool", 2, "NWDamaged" )
|
||||
self:NetworkVar( "Bool", 3, "WheelChainMode" )
|
||||
|
||||
if SERVER then
|
||||
self:SetMaxHP( 100 )
|
||||
self:SetHP( 100 )
|
||||
self:SetWidth( 3 )
|
||||
|
||||
self:NetworkVarNotify( "HP", self.HealthValueChanged )
|
||||
end
|
||||
end
|
||||
|
||||
function ENT:GetDamaged()
|
||||
return self:GetNWDamaged()
|
||||
end
|
||||
|
||||
function ENT:VelToRPM( speed )
|
||||
if not speed then return 0 end
|
||||
|
||||
return speed * 60 / math.pi / (self:GetRadius() * 2)
|
||||
end
|
||||
|
||||
function ENT:RPMToVel( rpm )
|
||||
if not rpm then return 0 end
|
||||
|
||||
return (math.pi * rpm * self:GetRadius() * 2) / 60
|
||||
end
|
||||
|
||||
function ENT:CheckAlignment()
|
||||
self.CamberCasterToe = (math.abs( self:GetToe() ) + math.abs( self:GetCaster() ) + math.abs( self:GetCamber() )) ~= 0
|
||||
|
||||
if CLIENT then return end
|
||||
|
||||
local SteerType = self:GetSteerType()
|
||||
local Caster = self:GetCaster()
|
||||
|
||||
local Camber = math.abs( self:GetCamber() )
|
||||
local CamberValue1 = (math.min( Camber, 15 ) / 15) * 0.3
|
||||
local CamberValue2 = (math.Clamp( Camber - 15, 0, 65 ) / 65) * 0.7
|
||||
|
||||
local CasterValue = (math.min( math.abs( Caster ), 15 ) / 15) * math.max( 1 - Camber / 10, 0 )
|
||||
|
||||
if SteerType == LVS.WHEEL_STEER_NONE then CasterValue = 0 end
|
||||
|
||||
if SteerType == LVS.WHEEL_STEER_FRONT and Caster < 0 then CasterValue = 0 end
|
||||
|
||||
if SteerType == LVS.WHEEL_STEER_REAR and Caster > 0 then CasterValue = 0 end
|
||||
|
||||
local TractionValue = 1 - CamberValue1 - CamberValue2 + CasterValue
|
||||
|
||||
self:PhysicsMaterialUpdate( TractionValue )
|
||||
|
||||
return TractionValue
|
||||
end
|
||||
@@ -0,0 +1,58 @@
|
||||
|
||||
AccessorFunc(ENT, "axle", "Axle", FORCE_NUMBER)
|
||||
|
||||
function ENT:SetMaster( master )
|
||||
self._Master = master
|
||||
end
|
||||
|
||||
function ENT:GetMaster()
|
||||
return self._Master
|
||||
end
|
||||
|
||||
function ENT:GetDirectionAngle()
|
||||
if not IsValid( self._Master ) then return angle_zero end
|
||||
|
||||
return self._Master:GetAngles()
|
||||
end
|
||||
|
||||
function ENT:GetRotationAxis()
|
||||
local WorldAngleDirection = -self:WorldToLocalAngles( self:GetDirectionAngle() )
|
||||
|
||||
return WorldAngleDirection:Right()
|
||||
end
|
||||
|
||||
function ENT:GetSteerType()
|
||||
if self._steerType then return self._steerType end
|
||||
|
||||
local base = self:GetBase()
|
||||
|
||||
if not IsValid( base ) then return 0 end
|
||||
|
||||
self._steerType = base:GetAxleData( self:GetAxle() ).SteerType
|
||||
|
||||
return self._steerType
|
||||
end
|
||||
|
||||
function ENT:GetTorqueFactor()
|
||||
if self._torqueFactor then return self._torqueFactor end
|
||||
|
||||
local base = self:GetBase()
|
||||
|
||||
if not IsValid( base ) then return 0 end
|
||||
|
||||
self._torqueFactor = base:GetAxleData( self:GetAxle() ).TorqueFactor or 0
|
||||
|
||||
return self._torqueFactor
|
||||
end
|
||||
|
||||
function ENT:GetBrakeFactor()
|
||||
if self._brakeFactor then return self._brakeFactor end
|
||||
|
||||
local base = self:GetBase()
|
||||
|
||||
if not IsValid( base ) then return 0 end
|
||||
|
||||
self._brakeFactor = base:GetAxleData( self:GetAxle() ).BrakeFactor or 0
|
||||
|
||||
return self._brakeFactor
|
||||
end
|
||||
@@ -0,0 +1,81 @@
|
||||
|
||||
function ENT:SetHandbrake( enable )
|
||||
if enable then
|
||||
|
||||
self:EnableHandbrake()
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
self:ReleaseHandbrake()
|
||||
end
|
||||
|
||||
function ENT:IsHandbrakeActive()
|
||||
return self._handbrakeActive == true
|
||||
end
|
||||
|
||||
function ENT:EnableHandbrake()
|
||||
if self._handbrakeActive then return end
|
||||
|
||||
self._handbrakeActive = true
|
||||
|
||||
self:LockRotation()
|
||||
end
|
||||
|
||||
function ENT:ReleaseHandbrake()
|
||||
if not self._handbrakeActive then return end
|
||||
|
||||
self._handbrakeActive = nil
|
||||
|
||||
self:ReleaseRotation()
|
||||
end
|
||||
|
||||
function ENT:LockRotation( TimedLock )
|
||||
|
||||
if TimedLock then
|
||||
self._RotationLockTime = CurTime() + 0.15
|
||||
end
|
||||
|
||||
if self:IsRotationLocked() then return end
|
||||
|
||||
local Master = self:GetMaster()
|
||||
|
||||
if not IsValid( Master ) then return end
|
||||
|
||||
self.bsLock = constraint.AdvBallsocket(self,Master,0,0,vector_origin,vector_origin,0,0,-0.1,-0.1,-0.1,0.1,0.1,0.1,0,0,0,1,1)
|
||||
self.bsLock.DoNotDuplicate = true
|
||||
|
||||
local PhysObj = self:GetPhysicsObject()
|
||||
|
||||
if self._OriginalMass or not IsValid( PhysObj ) then return end
|
||||
|
||||
local Mass = PhysObj:GetMass()
|
||||
|
||||
self._OriginalMass = Mass
|
||||
|
||||
PhysObj:SetMass( Mass * 2 )
|
||||
end
|
||||
|
||||
function ENT:ReleaseRotation()
|
||||
if self._RotationLockTime then
|
||||
if self._RotationLockTime > CurTime() then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if not self:IsRotationLocked() then return end
|
||||
|
||||
self.bsLock:Remove()
|
||||
|
||||
local PhysObj = self:GetPhysicsObject()
|
||||
|
||||
if not IsValid( PhysObj ) or not self._OriginalMass then return end
|
||||
|
||||
PhysObj:SetMass( self._OriginalMass )
|
||||
|
||||
self._OriginalMass = nil
|
||||
end
|
||||
|
||||
function ENT:IsRotationLocked()
|
||||
return IsValid( self.bsLock )
|
||||
end
|
||||
@@ -0,0 +1,216 @@
|
||||
|
||||
ENT.DSDamageAllowedType = DMG_SLASH + DMG_AIRBOAT + DMG_BULLET + DMG_SNIPER + DMG_BUCKSHOT + DMG_PREVENT_PHYSICS_FORCE
|
||||
ENT.DSArmorIgnoreForce = 0
|
||||
|
||||
function ENT:OnTakeDamage( dmginfo )
|
||||
local base = self:GetBase()
|
||||
|
||||
if not IsValid( base ) then return end
|
||||
|
||||
if self:GetWheelChainMode() then
|
||||
if dmginfo:IsDamageType( DMG_PREVENT_PHYSICS_FORCE ) then return end
|
||||
|
||||
base:OnTakeDamage( dmginfo )
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local MaxArmor = self:GetMaxHP()
|
||||
local Damage = dmginfo:GetDamage()
|
||||
|
||||
if not dmginfo:IsDamageType( DMG_PREVENT_PHYSICS_FORCE ) then
|
||||
local IsFireDamage = dmginfo:IsDamageType( DMG_BURN )
|
||||
|
||||
if dmginfo:GetDamageForce():Length() < self.DSArmorIgnoreForce and not IsFireDamage then return end
|
||||
|
||||
local MaxHealth = base:GetMaxHP()
|
||||
|
||||
local ArmoredHealth = MaxHealth + MaxArmor
|
||||
local NumShotsToKill = ArmoredHealth / Damage
|
||||
|
||||
local ScaleDamage = math.Clamp( MaxHealth / (NumShotsToKill * Damage),0,1)
|
||||
|
||||
dmginfo:ScaleDamage( ScaleDamage )
|
||||
|
||||
base:OnTakeDamage( dmginfo )
|
||||
end
|
||||
|
||||
if not dmginfo:IsDamageType( self.DSDamageAllowedType ) then return end
|
||||
|
||||
if not isnumber( base.WheelPhysicsTireHeight ) or base.WheelPhysicsTireHeight <= 0 then return end
|
||||
|
||||
local CurHealth = self:GetHP()
|
||||
|
||||
local NewHealth = math.Clamp( CurHealth - Damage, 0, MaxArmor )
|
||||
|
||||
self:SetHP( NewHealth )
|
||||
|
||||
if NewHealth > 0 then
|
||||
self:StartLeakAir()
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
self:DestroyTire()
|
||||
end
|
||||
|
||||
function ENT:StartLeakAir()
|
||||
if self._IsLeakingAir then return end
|
||||
|
||||
self._IsLeakingAir = true
|
||||
|
||||
local ID = "lvsLeakAir"..self:EntIndex()
|
||||
|
||||
timer.Create( ID, 0.2, 0, function()
|
||||
if not IsValid( self ) then timer.Remove( ID ) return end
|
||||
|
||||
local dmg = DamageInfo()
|
||||
dmg:SetDamage( 1 )
|
||||
dmg:SetAttacker( self )
|
||||
dmg:SetInflictor( self )
|
||||
dmg:SetDamageType( DMG_PREVENT_PHYSICS_FORCE )
|
||||
self:TakeDamageInfo( dmg )
|
||||
end)
|
||||
end
|
||||
|
||||
function ENT:StopLeakAir()
|
||||
if not self._IsLeakingAir then return end
|
||||
|
||||
local ID = "lvsLeakAir"..self:EntIndex()
|
||||
|
||||
timer.Remove( ID )
|
||||
|
||||
self._IsLeakingAir = nil
|
||||
end
|
||||
|
||||
function ENT:HealthValueChanged( name, old, new)
|
||||
if new == old or old > new or new ~= self:GetMaxHP() then return end
|
||||
|
||||
self:RepairTire()
|
||||
end
|
||||
|
||||
function ENT:DestroyTire()
|
||||
if self:GetNWDamaged() then return end
|
||||
|
||||
self:SetNWDamaged( true )
|
||||
|
||||
self:StopLeakAir()
|
||||
|
||||
local base = self:GetBase()
|
||||
local PhysObj = self:GetPhysicsObject()
|
||||
|
||||
if not IsValid( base ) or not IsValid( PhysObj ) then return end
|
||||
|
||||
self._OldTirePhysProp = PhysObj:GetMaterial()
|
||||
|
||||
PhysObj:SetMaterial( "glass" )
|
||||
|
||||
self:EmitSound("lvs/wheel_pop"..math.random(1,4)..".ogg")
|
||||
|
||||
local effectdata = EffectData()
|
||||
effectdata:SetOrigin( self:GetPos() )
|
||||
effectdata:SetEntity( self )
|
||||
util.Effect( "lvs_tire_blow", effectdata, true, true )
|
||||
|
||||
self._RestoreBodyGroups = {}
|
||||
|
||||
for id, group in pairs( self:GetBodyGroups() ) do
|
||||
for subid, subgroup in pairs( group.submodels ) do
|
||||
if subgroup == "" or string.lower( subgroup ) == "empty" then
|
||||
|
||||
local bodyGroupId = id - 1
|
||||
|
||||
self._RestoreBodyGroups[ bodyGroupId ] = self:GetBodygroup( bodyGroupId )
|
||||
|
||||
self:SetBodygroup( bodyGroupId, subid )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not IsValid( self.SuspensionConstraintElastic ) then return end
|
||||
|
||||
local Length = (self.SuspensionConstraintElastic:GetTable().length or 25) - base.WheelPhysicsTireHeight
|
||||
|
||||
self.SuspensionConstraintElastic:Fire( "SetSpringLength", math.max( Length - base.WheelPhysicsTireHeight , 1 ) )
|
||||
end
|
||||
|
||||
function ENT:RepairTire()
|
||||
self:StopLeakAir()
|
||||
|
||||
if not self._OldTirePhysProp then return end
|
||||
|
||||
if istable( self._RestoreBodyGroups ) then
|
||||
for bodyGroupId, subid in pairs( self._RestoreBodyGroups ) do
|
||||
self:SetBodygroup( bodyGroupId, subid )
|
||||
end
|
||||
|
||||
self._RestoreBodyGroups = nil
|
||||
end
|
||||
|
||||
self:SetNWDamaged( false )
|
||||
self:SetSuspensionHeight( self._SuspensionHeightMultiplier )
|
||||
|
||||
local PhysObj = self:GetPhysicsObject()
|
||||
|
||||
if not IsValid( PhysObj ) or PhysObj:GetMaterial() ~= "glass" then
|
||||
goto FinishRepairTire
|
||||
end
|
||||
|
||||
PhysObj:SetMaterial( self._OldTirePhysProp )
|
||||
|
||||
-- coders from the industry hate this, so we use it intentionally to assert dominance
|
||||
:: FinishRepairTire ::
|
||||
|
||||
self._OldTirePhysProp = nil
|
||||
end
|
||||
|
||||
function ENT:IsTireDestroyed()
|
||||
return isstring( self._OldTirePhysProp )
|
||||
end
|
||||
|
||||
function ENT:SetDamaged( new )
|
||||
if new == self:GetNWDamaged() then return end
|
||||
|
||||
self:SetNWDamaged( new )
|
||||
|
||||
if new then
|
||||
if not self._torqueFactor or self.old_torqueFactor then return end
|
||||
|
||||
self.old_torqueFactor = self._torqueFactor
|
||||
self._torqueFactor = self._torqueFactor * 0.25
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if not self.old_torqueFactor then return end
|
||||
|
||||
self._torqueFactor = self.old_torqueFactor
|
||||
self.old_torqueFactor = nil
|
||||
end
|
||||
|
||||
function ENT:Destroy()
|
||||
if self:GetDestroyed() then return end
|
||||
|
||||
self:SetDestroyed( true )
|
||||
self:SetDamaged( true )
|
||||
|
||||
local Master = self:GetMaster()
|
||||
|
||||
if not IsValid( Master ) or IsValid( self.bsLockDMG ) then return end
|
||||
|
||||
local Fric = 10
|
||||
|
||||
self.bsLockDMG = constraint.AdvBallsocket(self,Master,0,0,vector_origin,vector_origin,0,0,-180,-180,-180,180,180,180,Fric,Fric,Fric,1,1)
|
||||
self.bsLockDMG.DoNotDuplicate = true
|
||||
end
|
||||
|
||||
function ENT:Repair()
|
||||
self:SetHP( self:GetMaxHP() )
|
||||
|
||||
self:SetDestroyed( false )
|
||||
self:SetDamaged( false )
|
||||
|
||||
if IsValid( self.bsLockDMG ) then
|
||||
self.bsLockDMG:Remove()
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user