Files
wnsrc/lua/entities/gmod_sent_vehicle_fphysics_wheel.lua

664 lines
16 KiB
Lua
Raw Normal View History

2024-08-04 22:55:00 +03:00
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
AddCSLuaFile()
ENT.Type = "anim"
ENT.Spawnable = false
ENT.AdminSpawnable = false
ENT.DoNotDuplicate = true
ENT.RenderGroup = RENDERGROUP_BOTH
function ENT:SetupDataTables()
self:NetworkVar( "Float", 0, "Radius")
self:NetworkVar( "Float", 1, "OnGround" )
self:NetworkVar( "Float", 2, "RPM" )
self:NetworkVar( "Float", 3, "GripLoss" )
self:NetworkVar( "Float", 4, "Speed" )
self:NetworkVar( "Float", 5, "SkidSound" )
self:NetworkVar( "String", 2, "SurfaceMaterial" )
self:NetworkVar( "Bool", 1, "Damaged" )
self:NetworkVar( "Entity", 1, "BaseEnt" )
if SERVER then
self:NetworkVarNotify( "Damaged", self.OnDamaged )
end
end
function ENT:GetWidth()
return 3
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
if SERVER then
function ENT:Initialize()
self:SetModel( "models/props_vehicles/tire001c_car.mdl" )
self:PhysicsInit( SOLID_VPHYSICS )
self:SetMoveType( MOVETYPE_VPHYSICS )
self:SetSolid( SOLID_VPHYSICS )
self:SetCollisionGroup( COLLISION_GROUP_WEAPON )
self:SetUseType( SIMPLE_USE )
self:AddFlags( FL_OBJECT )
self:AddEFlags( EFL_NO_PHYSCANNON_INTERACTION )
self:DrawShadow( false )
self.OldMaterial = ""
self.OldMaterial2 = ""
self.OldVar = 0
self.OldVar2 = 0
local Color = self:GetColor()
local dot = Color.r * Color.g * Color.b * Color.a
self.OldColor = dot
end
function ENT:Use( ply )
local base = self:GetBaseEnt()
if not IsValid( base ) then return end
base:Use( ply )
end
function ENT:Think()
if self.GhostEnt then
local Color = self:GetColor()
local dot = Color.r * Color.g * Color.b * Color.a
if dot ~= self.OldColor then
if IsValid( self.GhostEnt ) then
self.GhostEnt:SetColor( Color )
self.GhostEnt:SetRenderMode( self:GetRenderMode() )
end
self.OldColor = dot
end
end
self:NextThink( CurTime() + 0.15 )
return true
end
function ENT:FixTire()
self:SetDamaged( false )
if not self.PreBreak then return end
self.PreBreak:Stop()
self.PreBreak = nil
end
function ENT:OnRemove()
if self.PreBreak then
self.PreBreak:Stop()
end
end
function ENT:PhysicsCollide( data, physobj )
if data.Speed > 100 and data.DeltaTime > 0.2 then
if data.Speed > 400 then
self:EmitSound( "Rubber_Tire.ImpactHard" )
self:EmitSound( "simulated_vehicles/suspension_creak_".. math.random(1,6) ..".ogg" )
else
self:EmitSound( "Rubber.ImpactSoft" )
end
end
end
function ENT:OnTakeDamage( dmginfo )
local BaseEnt = self:GetBaseEnt()
if IsValid( BaseEnt ) and dmginfo:IsDamageType( DMG_AIRBOAT + DMG_BULLET ) then
BaseEnt:OnTakeDamage( dmginfo )
end
if self:GetDamaged() or not simfphys.DamageEnabled then return end
local Damage = dmginfo:GetDamage()
local DamagePos = dmginfo:GetDamagePosition()
if not IsValid( BaseEnt ) or BaseEnt:GetBulletProofTires() or Damage <= 1 then return end
if not self.PreBreak then
self.PreBreak = CreateSound(self, "ambient/gas/cannister_loop.wav")
self.PreBreak:PlayEx(0.5,100)
timer.Simple(math.Rand(0.5,5), function()
if IsValid(self) and not self:GetDamaged() then
self:SetDamaged( true )
if self.PreBreak then
self.PreBreak:Stop()
self.PreBreak = nil
end
end
end)
else
self:SetDamaged( true )
self.PreBreak:Stop()
self.PreBreak = nil
end
end
function ENT:OnDamaged( name, old, new)
if new == old then return end
if new == true then
self.dRadius = self:BoundingRadius() * 0.28
self:EmitSound( "simulated_vehicles/sfx/tire_break.ogg" )
if IsValid( self.GhostEnt ) then
self.GhostEnt:SetParent( nil )
self.GhostEnt:GetPhysicsObject():EnableMotion( false )
self.GhostEnt:SetPos( self:LocalToWorld( Vector(0,0,-self.dRadius) ) )
self.GhostEnt:SetParent( self )
end
else
if IsValid( self.GhostEnt ) then
self.GhostEnt:SetParent( nil )
self.GhostEnt:GetPhysicsObject():EnableMotion( false )
self.GhostEnt:SetPos( self:LocalToWorld( Vector(0,0,0) ) )
self.GhostEnt:SetParent( self )
end
end
local BaseEnt = self:GetBaseEnt()
if not IsValid( BaseEnt ) then return end
BaseEnt:SetSuspension( self.Index , new )
end
return
end
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 = {
["concrete"] = true,
["tile"] = true,
["metal"] = true,
["boulder"] = true,
["default"] = true,
}
ENT.DustEffectSurfaces = {
["sand"] = true,
["dirt"] = true,
["grass"] = true,
["antlionsand"] = true,
}
function ENT:Initialize()
self.FadeHeat = 0
timer.Simple( 0.01, function()
if not IsValid( self ) then return end
self.Radius = self:BoundingRadius()
end)
end
function ENT:Think()
self:ManageSmoke()
local T = CurTime()
if (self.fxTimer or 0) < T then
self:CalcWheelSlip()
self.fxTimer = T + 0.1
end
self:SetNextClientThink( CurTime() + 0.005 )
return true
end
function ENT:ManageSmoke()
local BaseEnt = self:GetBaseEnt()
if not IsValid( BaseEnt ) then return end
if LocalPlayer():GetPos():DistToSqr(self:GetPos()) > 6000 * 6000 or not BaseEnt:GetActive() then return end
local WheelOnGround = self:GetOnGround()
local GripLoss = self:GetGripLoss()
local Material = self:GetSurfaceMaterial()
if WheelOnGround > 0 and (Material == "concrete" or Material == "rock" or Material == "tile") and GripLoss > 0 then
self.FadeHeat = math.Clamp( self.FadeHeat + GripLoss * 0.06,0,10)
else
self.FadeHeat = self.FadeHeat * 0.995
end
local Scale = self.FadeHeat ^ 3 * 0.001
local SmokeOn = (self.FadeHeat >= 7)
local DirtOn = GripLoss > 0.05
local lcolor = BaseEnt:GetTireSmokeColor() * 255
local Speed = self:GetVelocity():Length()
local OnRim = self:GetDamaged()
local Forward = self:GetForward()
local Dir = (BaseEnt:GetGear() < 2) and Forward or -Forward
local WheelSize = self.Radius or 0
local Pos = self:GetPos()
if SmokeOn and not OnRim then
local effectdata = EffectData()
effectdata:SetOrigin( Pos )
effectdata:SetNormal( Dir )
effectdata:SetMagnitude( Scale )
effectdata:SetRadius( WheelSize )
effectdata:SetStart( Vector( lcolor.r, lcolor.g, lcolor.b ) )
util.Effect( "simfphys_tiresmoke", effectdata )
end
if WheelOnGround == 0 then return end
if (Speed > 150 or DirtOn) and OnRim then
self:MakeSparks( GripLoss, Dir, Pos, WheelSize )
end
end
function ENT:MakeSparks( Scale, Dir, Pos, WheelSize )
self.NextSpark = self.NextSpark or 0
if self.NextSpark < CurTime() then
self.NextSpark = CurTime() + 0.1
local effectdata = EffectData()
effectdata:SetOrigin( Pos - Vector(0,0,WheelSize * 0.5) )
effectdata:SetNormal( (Dir + Vector(0,0,0.5)) * Scale * 0.5)
util.Effect( "manhacksparks", effectdata, true, true )
end
end
function ENT:Draw()
end
function ENT:DrawTranslucent()
self:CalcWheelEffects()
end
function ENT:OnRemove()
self:StopWheelEffects()
end
function ENT:CalcWheelSlip()
local Base = self:GetBaseEnt()
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() )
self._WheelSlip = math.max( rpm - rpmTheoretical - 250, 0 ) ^ 2 + math.max( math.abs( Base:VectorSplitNormal( self:GetRight(), Vel * 4 ) ) - VelLength, 0 )
self._WheelSkid = VelLength + self._WheelSlip
end
function ENT:GetSlip()
return (self._WheelSlip or 0)
end
function ENT:GetSkid()
return (self._WheelSkid or 0)
end
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( "simfphys_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( "simfphys_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( "simfphys_physics_wheeldust", effectdata, true, true )
end
end
function ENT:CalcWheelEffects()
local T = CurTime()
if (self._NextFx or 0) > T then return end
self._NextFx = T + 0.05
local Base = self:GetBaseEnt()
if not IsValid( Base ) then return end
local Radius = Base:GetUp() * (self:GetRadius() + 1)
local Pos = self:GetPos() + self:GetVelocity() * 0.025
local StartPos = Pos + Radius
local EndPos = Pos - Radius
local trace = util.TraceLine( {
start = StartPos,
endpos = EndPos,
filter = Base:GetCrosshairFilterEnts(),
} )
local traceWater = util.TraceLine( {
start = StartPos,
endpos = EndPos,
filter = Base:GetCrosshairFilterEnts(),
mask = MASK_WATER,
} )
self:CalcWheelSounds( Base, trace, traceWater )
if traceWater.Hit and trace.HitPos.z < traceWater.HitPos.z then
if math.abs( self:GetRPM() ) > 25 then
local effectdata = EffectData()
effectdata:SetOrigin( traceWater.Fraction > 0.5 and traceWater.HitPos or Pos )
effectdata:SetEntity( Base )
effectdata:SetMagnitude( self:BoundingRadius() )
effectdata:SetFlags( 0 )
util.Effect( "simfphys_physics_wheelwatersplash", effectdata )
end
end
if self:GetSlip() < 500 or self:GetDamaged() then self:StopWheelEffects() return end
self:StartWheelEffects( Base, trace, traceWater )
end
function ENT:CalcWheelSounds( Base, trace, traceWater )
if not trace.Hit then return end
-- TODO: fix reason for this workaround
if trace.Entity == self then
if istable( Base.CrosshairFilterEnts ) and #Base.CrosshairFilterEnts > 1 then
Base.CrosshairFilterEnts = nil
end
return
end
local RPM = math.abs( self:GetRPM() )
if RPM > 50 then
if traceWater.Hit then
Base:DoTireSound( "roll_wet" )
return
end
if self:GetDamaged() then
Base:DoTireSound( "roll_damaged" )
return
end
local surface = self.DustEffectSurfaces[ util.GetSurfacePropName( trace.SurfaceProps ) ] and "_dirt" or ""
local snd_type = (self:GetSlip() > 500) and "skid" or "roll"
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
Base:DoTireSound( snd_type..surface )
end
end
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,
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
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_fakephysics_skidmarks", function( bDepth, bSkybox )
if bSkybox then return end
render.SetColorMaterial()
for _, wheel in ipairs( ents.FindByClass("gmod_sent_vehicle_fphysics_wheel") ) do
wheel:RenderSkidMarks()
end
end)