--[[ | 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/ --]] local LVS = LVS LVS._ActiveBullets = {} function LVS:GetBullet( index ) if not LVS._ActiveBullets then return end return LVS._ActiveBullets[ index ] end local NewBullet = {} NewBullet.__index = NewBullet function NewBullet:SetPos( pos ) self.curpos = pos end function NewBullet:GetPos() if not self.curpos then return self.Src end return self.curpos end function NewBullet:GetDir() return self.Dir or vector_origin end function NewBullet:GetTimeAlive() return CurTime() - self.StartTime end function NewBullet:GetSpawnTime() if SERVER then return self.StartTime else return math.min( self.StartTimeCL, CurTime() ) -- time when the bullet is received on client end end function NewBullet:GetLength() return math.min((CurTime() - self:GetSpawnTime()) * 14,1) end local function HandleBullets() local T = CurTime() local FT = FrameTime() for id, bullet in pairs( LVS._ActiveBullets ) do -- loop through bullet table if bullet:GetSpawnTime() + 5 < T then -- destroy all bullets older than 5 seconds LVS._ActiveBullets[ id ] = nil continue end local start = bullet.Src local dir = bullet.Dir local TimeAlive = bullet:GetTimeAlive() local pos = dir * TimeAlive * bullet.Velocity local mul = bullet:GetLength() local Is2ndTickAlive = TimeAlive > FT * 2 -- this system is slow. Takes atleast 2 ticks before it spawns. We need to trace from startpos until lua catches up -- startpos, direction and curtime of creation is networked to client. -- The Bullet position is simulated by doing startpos + dir * time * velocity if SERVER then bullet:SetPos( start + pos ) else if IsValid( bullet.Entity ) then -- if the vehicle entity is valid... local inv = 1 - mul -- ..."parent" the bullet to the vehicle for a very short time. This will give the illusion of the bullet not lagging behind even tho it is fired later on client bullet:SetPos( start * mul + bullet.Entity:LocalToWorld( bullet.SrcEntity ) * inv + pos ) else bullet:SetPos( start + pos ) end end local Filter = bullet.Filter local trace = util.TraceHull( { start = Is2ndTickAlive and start + pos - dir or start, endpos = start + pos + dir * bullet.Velocity * FT, filter = Filter, mins = bullet.Mins, maxs = bullet.Maxs, mask = MASK_SHOT_HULL } ) --debugoverlay.Line( Is2ndTickAlive and start + pos - dir or start, start + pos + dir * bullet.Velocity * FT, Color( 255, 255, 255 ), true ) if CLIENT then if not bullet.Muted and mul == 1 and LVS.EnableBulletNearmiss then -- whats more expensive, spamming this effect or doing distance checks to localplayer for each bullet think? Alternative method? local effectdata = EffectData() effectdata:SetOrigin( bullet:GetPos() ) effectdata:SetFlags( 2 ) util.Effect( "TracerSound", effectdata ) end if not bullet.HasHitWater then local traceWater = util.TraceLine( { start = start + pos - dir, endpos = start + pos + dir * bullet.Velocity * FT, filter = Filter, mask = MASK_WATER, } ) if traceWater.Hit then LVS._ActiveBullets[ id ].HasHitWater = true local effectdata = EffectData() effectdata:SetOrigin( traceWater.HitPos ) effectdata:SetScale( 10 + bullet.HullSize * 0.5 ) effectdata:SetFlags( 2 ) util.Effect( "WaterSplash", effectdata, true, true ) end end end if trace.Hit then -- hulltrace doesnt hit the wall due to its hullsize... -- so this needs an extra trace line local traceImpact = util.TraceLine( { start = Is2ndTickAlive and start + pos - dir or start, endpos = start + pos + dir * 250, filter = Filter, mask = MASK_SHOT_HULL } ) if SERVER then local EndPos = traceImpact.Hit and traceImpact.HitPos or trace.HitPos local dmginfo = DamageInfo() dmginfo:SetDamage( bullet.Damage ) dmginfo:SetAttacker( (IsValid( bullet.Attacker ) and bullet.Attacker) or (IsValid( bullet.Entity ) and bullet.Entity) or game.GetWorld() ) dmginfo:SetDamageType( DMG_AIRBOAT ) dmginfo:SetInflictor( (IsValid( bullet.Entity ) and bullet.Entity) or (IsValid( bullet.Attacker ) and bullet.Attacker) or game.GetWorld() ) dmginfo:SetDamagePosition( EndPos ) dmginfo:SetDamageForce( bullet.Dir * bullet.Force ) if bullet.Callback then bullet.Callback( bullet.Attacker, traceImpact, dmginfo ) end trace.Entity:TakeDamageInfo( dmginfo ) if IsValid( trace.Entity ) and trace.Entity.GetBloodColor then local BloodColor = trace.Entity:GetBloodColor() if BloodColor and BloodColor ~= DONT_BLEED then local effectdata = EffectData() effectdata:SetOrigin( EndPos ) effectdata:SetColor( BloodColor ) util.Effect( "BloodImpact", effectdata, true, true ) end end if bullet.SplashDamage and bullet.SplashDamageRadius then local effectdata = EffectData() effectdata:SetOrigin( EndPos ) effectdata:SetNormal( trace.HitWorld and trace.HitNormal or dir ) effectdata:SetMagnitude( bullet.SplashDamageRadius / 250 ) util.Effect( bullet.SplashDamageEffect, effectdata ) dmginfo:SetDamageType( bullet.SplashDamageType ) dmginfo:SetDamage( bullet.SplashDamage ) util.BlastDamageInfo( dmginfo, EndPos, bullet.SplashDamageRadius ) end else if not traceImpact.HitSky then local effectdata = EffectData() effectdata:SetOrigin( traceImpact.HitPos ) effectdata:SetEntity( traceImpact.Entity ) effectdata:SetStart( start ) effectdata:SetNormal( traceImpact.HitNormal ) effectdata:SetSurfaceProp( traceImpact.SurfaceProps ) util.Effect( "Impact", effectdata ) end end LVS._ActiveBullets[ id ] = nil end end end local vector_one = Vector(1,1,1) if SERVER then util.AddNetworkString( "lvs_fire_bullet" ) hook.Add( "Tick", "!!!!lvs_bullet_handler", function( ply, ent ) -- from what i understand, think can "skip" on lag, while tick still simulates all steps HandleBullets() end ) function LVS:FireBullet( data ) local bullet = {} setmetatable( bullet, NewBullet ) bullet.TracerName = data.TracerName or "lvs_tracer_orange" bullet.Src = data.Src or vector_origin bullet.Dir = (data.Dir + VectorRand() * (data.Spread or vector_origin) * 0.5):GetNormalized() bullet.Force = data.Force or 10 bullet.HullSize = data.HullSize or 5 bullet.Mins = -vector_one * bullet.HullSize bullet.Maxs = vector_one * bullet.HullSize bullet.Velocity = data.Velocity or 2500 bullet.Attacker = IsValid( data.Attacker ) and data.Attacker or (IsValid( data.Entity ) and data.Entity or game.GetWorld()) bullet.Damage = data.Damage or 10 bullet.Entity = data.Entity if IsValid( bullet.Entity ) and bullet.Entity.GetCrosshairFilterEnts then bullet.Filter = bullet.Entity:GetCrosshairFilterEnts() else bullet.Filter = bullet.Entity end bullet.SrcEntity = data.SrcEntity or vector_origin bullet.Callback = data.Callback bullet.SplashDamage = data.SplashDamage bullet.SplashDamageRadius = data.SplashDamageRadius bullet.SplashDamageEffect = data.SplashDamageEffect or "lvs_bullet_impact" bullet.SplashDamageType = data.SplashDamageType or DMG_SONIC bullet.StartTime = CurTime() local ReplaceEffect = isstring( bullet.SplashDamageEffect ) if InfMap then for _, ply in ipairs( player.GetAll() ) do local NewPos = Vector( bullet.Src.x, bullet.Src.y, bullet.Src.z ) - InfMap.unlocalize_vector( Vector(), ply.CHUNK_OFFSET ) net.Start( "lvs_fire_bullet", true ) net.WriteString( bullet.TracerName ) net.WriteFloat( NewPos.x ) net.WriteFloat( NewPos.y ) net.WriteFloat( NewPos.z ) net.WriteAngle( bullet.Dir:Angle() ) net.WriteFloat( bullet.StartTime ) net.WriteFloat( bullet.HullSize ) net.WriteEntity( bullet.Entity ) net.WriteFloat( bullet.SrcEntity.x ) net.WriteFloat( bullet.SrcEntity.y ) net.WriteFloat( bullet.SrcEntity.z ) net.WriteFloat( bullet.Velocity ) net.Send( ply ) end else net.Start( "lvs_fire_bullet", true ) net.WriteString( bullet.TracerName ) net.WriteFloat( bullet.Src.x ) net.WriteFloat( bullet.Src.y ) net.WriteFloat( bullet.Src.z ) net.WriteAngle( bullet.Dir:Angle() ) net.WriteFloat( bullet.StartTime ) net.WriteFloat( bullet.HullSize ) net.WriteEntity( bullet.Entity ) net.WriteFloat( bullet.SrcEntity.x ) net.WriteFloat( bullet.SrcEntity.y ) net.WriteFloat( bullet.SrcEntity.z ) net.WriteFloat( bullet.Velocity ) net.SendPVS( bullet.Src ) end table.insert(LVS._ActiveBullets, bullet ) end else local Index = 0 local MaxIndex = 4094 -- this is the util.effect limit net.Receive( "lvs_fire_bullet", function( length ) local bullet = {} setmetatable( bullet, NewBullet ) bullet.TracerName = net.ReadString() bullet.Src = Vector(net.ReadFloat(),net.ReadFloat(),net.ReadFloat()) bullet.Dir = net.ReadAngle():Forward() bullet.StartTime = net.ReadFloat() bullet.HullSize = net.ReadFloat() bullet.Mins = -vector_one * bullet.HullSize bullet.Maxs = vector_one * bullet.HullSize bullet.Entity = net.ReadEntity() if IsValid( bullet.Entity ) and bullet.Entity.GetCrosshairFilterEnts then bullet.Filter = bullet.Entity:GetCrosshairFilterEnts() else bullet.Filter = bullet.Entity end bullet.SrcEntity = Vector(net.ReadFloat(),net.ReadFloat(),net.ReadFloat()) bullet.Velocity = net.ReadFloat() if net.ReadBool() then bullet.SplashDamageEffect = net.ReadString() else bullet.SplashDamageEffect = "lvs_bullet_impact" end bullet.StartTimeCL = CurTime() + RealFrameTime() local ply = LocalPlayer() bullet.Muted = IsValid( ply ) and bullet.Entity == ply:lvsGetVehicle() Index = Index + 1 if Index > MaxIndex then Index = 1 end LVS._ActiveBullets[ Index ] = bullet local effectdata = EffectData() effectdata:SetOrigin( bullet.Src ) effectdata:SetNormal( bullet.Dir ) effectdata:SetMaterialIndex( Index ) util.Effect( bullet.TracerName, effectdata ) end ) hook.Add( "Think", "!!!!_lvs_bullet_think_cl", function() HandleBullets() end ) end