code_gs

Custom FireBullets

Mar 27th, 2020 (edited)
507
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 38.85 KB | None | 0 0
  1. gs = {
  2.     random = include("minstd.lua") -- from https://github.com/Kefta/Lua-MINSTD
  3. }
  4.  
  5.  
  6. DEBUG_LENGTH = 3
  7.  
  8. COORD_EXTENT = 2 * 16384
  9. // Maximum traceable distance (assumes cubic world and trace from one corner to opposite)
  10. // COORD_EXTENT * sqrt(3)
  11. MAX_TRACE_LENGTH = math.sqrt(3) * COORD_EXTENT
  12.  
  13. SF_BREAK_NO_BULLET_PENETRATION = 0x0800
  14.  
  15. -- From the math lib
  16. do
  17.     local band = bit.band
  18.     local bnot = bit.bnot
  19.     local bor = bit.bor
  20.     local bxor = bit.bxor
  21.     local rshift = bit.rshift
  22.     local floor = math.floor
  23.     local log = math.log
  24.     local sin = math.sin
  25.     local cos = math.cos
  26.     local rad = math.rad
  27.     local abs = math.abs
  28.     local exp = math.exp
  29.  
  30.     // The four core functions - F1 is optimized somewhat
  31.     // local function f1(x, y, z) bit.bor(bit.band(x, y), bit.band(bit.bnot(x), z)) end
  32.     // This is the central step in the MD5 algorithm.
  33.     local function Step1(w, x, y, z, flData, iStep)
  34.         w = w + bxor(z, band(x, bxor(y, z))) + flData
  35.        
  36.         return bor((w * 2^iStep) % 0x100000000, floor(w % 0x100000000 / 2^(0x20 - iStep))) + x
  37.     end
  38.  
  39.     local function Step2(w, x, y, z, flData, iStep)
  40.         w = w + bxor(y, band(z, bxor(x, y))) + flData
  41.        
  42.         return bor((w * 2^iStep) % 0x100000000, floor(w % 0x100000000 / 2^(0x20 - iStep))) + x
  43.     end
  44.  
  45.     local function Step3(w, x, y, z, flData, iStep)
  46.         w = w + bxor(bxor(x, y), z) + flData
  47.        
  48.         return bor((w * 2^iStep) % 0x100000000, floor(w % 0x100000000 / 2^(0x20 - iStep))) + x
  49.     end
  50.  
  51.     local function Step4(w, x, y, z, flData, iStep)
  52.         w = w + bxor(y, bor(x, bnot(z))) + flData
  53.        
  54.         return bor((w * 2^iStep) % 0x100000000, floor(w % 0x100000000 / 2^(0x20 - iStep))) + x
  55.     end
  56.  
  57.     function math.MD5Random(iSeed)
  58.         --gs.CheckType(iSeed, 1, TYPE_NUMBER)
  59.        
  60.         -- FIXME: https://github.com/Facepunch/garrysmod-issues/issues/2820
  61.         local bEnabled = jit.status()
  62.        
  63.         if (bEnabled) then
  64.             jit.off()
  65.         end
  66.        
  67.         iSeed = iSeed % 0x100000000
  68.        
  69.         local a = Step1(0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, iSeed + 0xd76aa478, 7)
  70.         local d = Step1(0x10325476, a, 0xefcdab89, 0x98badcfe, 0xe8c7b7d6, 12)
  71.         local c = Step1(0x98badcfe, d, a, 0xefcdab89, 0x242070db, 17)
  72.         local b = Step1(0xefcdab89, c, d, a, 0xc1bdceee, 22)
  73.         a = Step1(a, b, c, d, 0xf57c0faf, 7)
  74.         d = Step1(d, a, b, c, 0x4787c62a, 12)
  75.         c = Step1(c, d, a, b, 0xa8304613, 17)
  76.         b = Step1(b, c, d, a, 0xfd469501, 22)
  77.         a = Step1(a, b, c, d, 0x698098d8, 7)
  78.         d = Step1(d, a, b, c, 0x8b44f7af, 12)
  79.         c = Step1(c, d, a, b, 0xffff5bb1, 17)
  80.         b = Step1(b, c, d, a, 0x895cd7be, 22)
  81.         a = Step1(a, b, c, d, 0x6b901122, 7)
  82.         d = Step1(d, a, b, c, 0xfd987193, 12)
  83.         c = Step1(c, d, a, b, 0xa67943ae, 17)
  84.         b = Step1(b, c, d, a, 0x49b40821, 22)
  85.        
  86.         a = Step2(a, b, c, d, 0xf61e25e2, 5)
  87.         d = Step2(d, a, b, c, 0xc040b340, 9)
  88.         c = Step2(c, d, a, b, 0x265e5a51, 14)
  89.         b = Step2(b, c, d, a, iSeed + 0xe9b6c7aa, 20)
  90.         a = Step2(a, b, c, d, 0xd62f105d, 5)
  91.         d = Step2(d, a, b, c, 0x02441453, 9)
  92.         c = Step2(c, d, a, b, 0xd8a1e681, 14)
  93.         b = Step2(b, c, d, a, 0xe7d3fbc8, 20)
  94.         a = Step2(a, b, c, d, 0x21e1cde6, 5)
  95.         d = Step2(d, a, b, c, 0xc33707f6, 9)
  96.         c = Step2(c, d, a, b, 0xf4d50d87, 14)
  97.         b = Step2(b, c, d, a, 0x455a14ed, 20)
  98.         a = Step2(a, b, c, d, 0xa9e3e905, 5)
  99.         d = Step2(d, a, b, c, 0xfcefa3f8, 9)
  100.         c = Step2(c, d, a, b, 0x676f02d9, 14)
  101.         b = Step2(b, c, d, a, 0x8d2a4c8a, 20)
  102.  
  103.         a = Step3(a, b, c, d, 0xfffa3942, 4)
  104.         d = Step3(d, a, b, c, 0x8771f681, 11)
  105.         c = Step3(c, d, a, b, 0x6d9d6122, 16)
  106.         b = Step3(b, c, d, a, 0xfde5382c, 23)
  107.         a = Step3(a, b, c, d, 0xa4beeac4, 4)
  108.         d = Step3(d, a, b, c, 0x4bdecfa9, 11)
  109.         c = Step3(c, d, a, b, 0xf6bb4b60, 16)
  110.         b = Step3(b, c, d, a, 0xbebfbc70, 23)
  111.         a = Step3(a, b, c, d, 0x289b7ec6, 4)
  112.         d = Step3(d, a, b, c, iSeed + 0xeaa127fa, 11)
  113.         c = Step3(c, d, a, b, 0xd4ef3085, 16)
  114.         b = Step3(b, c, d, a, 0x04881d05, 23)
  115.         a = Step3(a, b, c, d, 0xd9d4d039, 4)
  116.         d = Step3(d, a, b, c, 0xe6db99e5, 11)
  117.         c = Step3(c, d, a, b, 0x1fa27cf8, 16)
  118.         b = Step3(b, c, d, a, 0xc4ac5665, 23)
  119.        
  120.         a = Step4(a, b, c, d, iSeed + 0xf4292244, 6)
  121.         d = Step4(d, a, b, c, 0x432aff97, 10)
  122.         c = Step4(c, d, a, b, 0xab9423c7, 15)
  123.         b = Step4(b, c, d, a, 0xfc93a039, 21)
  124.         a = Step4(a, b, c, d, 0x655b59c3, 6)
  125.         d = Step4(d, a, b, c, 0x8f0ccc92, 10)
  126.         c = Step4(c, d, a, b, 0xffeff47d, 15)
  127.         b = Step4(b, c, d, a, 0x85845e51, 21)
  128.         a = Step4(a, b, c, d, 0x6fa87e4f, 6)
  129.         d = Step4(d, a, b, c, 0xfe2ce6e0, 10)
  130.         c = Step4(c, d, a, b, 0xa3014314, 15)
  131.         b = Step4(b, c, d, a, 0x4e0811a1, 21)
  132.         a = Step4(a, b, c, d, 0xf7537e82, 6)
  133.         d = Step4(d, a, b, c, 0xbd3af235, 10)
  134.         c = Step4(c, d, a, b, 0x2ad7d2bb, 15)
  135.         b = (Step4(b, c, d, a, 0xeb86d391, 21) + 0xefcdab89) % 0x100000000
  136.        
  137.         c = (c + 0x98badcfe) % 0x100000000
  138.         a = floor(b / 0x10000) % 0x100 + floor(b / 0x1000000) % 0x100 * 0x100 + c % 0x100 * 0x10000 + floor(c / 0x100) % 0x100 * 0x1000000
  139.        
  140.         if (bEnabled) then
  141.             jit.on()
  142.         end
  143.        
  144.         return a
  145.     end
  146. end
  147.  
  148. -- From the Vector lib
  149. do
  150.     local VECTOR = FindMetaTable("Vector")
  151.    
  152.     function VECTOR:Right(vUp --[[= Vector(0, 0, 1)]])
  153.         if (self[1] == 0 and self[2] == 0)then
  154.             // pitch 90 degrees up/down from identity
  155.             return Vector(0, -1, 0)
  156.         end
  157.        
  158.         if (vUp == nil) then
  159.             vUp = vector_up
  160.         end
  161.        
  162.         local vRet = self:Cross(vUp)
  163.         vRet:Normalize()
  164.        
  165.         return vRet
  166.     end
  167.  
  168.     function VECTOR:Up(vUp --[[= Vector(0, 0, 1)]])
  169.         if (self[1] == 0 and self[2] == 0)then
  170.             return Vector(-self[3], 0, 0)
  171.         end
  172.        
  173.         if (vUp == nil) then
  174.             vUp = vector_up
  175.         end
  176.        
  177.         local vRet = self:Cross(vUp)
  178.         vRet = vRet:Cross(self)
  179.         vRet:Normalize()
  180.        
  181.         return vRet
  182.     end
  183. end
  184.  
  185. -- From the entity lib
  186. do
  187.     local ENTITY = FindMetaTable("Entity")
  188.  
  189.     function ENTITY:IsBreakable()
  190.         local sClass = self:GetClass()
  191.        
  192.         return sClass == "func_breakable" or sClass == "func_breakable_surf" or sClass == "func_physbox"
  193.     end
  194.  
  195.     -- FIXME: Make debugoverlay display transparent
  196.     function ENTITY:DrawHitBoxes(flDuration --[[= 0]])
  197.         local iSet = self:GetHitboxSet()
  198.        
  199.         if (iSet ~= nil) then
  200.             if (flDuration == nil) then
  201.                 flDuration = 0
  202.             end
  203.            
  204.             for iGroup = 0, self:GetHitBoxGroupCount() - 1 do
  205.                 for iHitBox = 0, self:GetHitBoxCount(iGroup) - 1 do
  206.                     local vPos, aRot = self:GetBonePosition(self:GetHitBoxBone(iHitBox, iGroup))
  207.                     local vMins, vMaxs = self:GetHitBoxBounds(iHitBox, iGroup)
  208.                     debugoverlay.BoxAngles(vPos, vMins, vMaxs, aRot, flDuration, color_debug)
  209.                 end
  210.             end
  211.         end
  212.     end
  213. end
  214.  
  215. local PLAYER = FindMetaTable("Player")
  216.  
  217. function PLAYER:GetMD5Seed()
  218.     local iCommandNumber = self:GetCurrentCommand():CommandNumber() -- FIXME: Change to FrameNumber when it is finally binded serverside
  219.    
  220.     if (self.m_iMD5SeedSavedNumber ~= iCommandNumber) then
  221.         self.m_iMD5SeedSavedNumber = iCommandNumber
  222.         self.m_iMD5Seed = math.MD5Random(iCommandNumber)
  223.     end
  224.    
  225.     return self.m_iMD5Seed
  226. end
  227.  
  228. -- Equivalent of CBasePlayer::EyeVectors() before AngleVecotrs
  229. function PLAYER:ActualEyeAngles()
  230.     local pVehicle = self:GetVehicle()
  231.    
  232.     if (pVehicle:IsValid()) then
  233.         local _, ang = pVehicle:GetVehicleViewPosition()
  234.        
  235.         return ang
  236.     end
  237.    
  238.     return self:EyeAngles()
  239. end
  240.  
  241. local vDefaultOffset = Vector(0, 0, -4)
  242.  
  243. function PLAYER:ComputeTracerStartPosition(vSrc, vOffset --[[= Vector(0, 0, -4)]], flForwardMul --[[= 16]], flRightMul --[[[= 2]])
  244.     if (vOffset == nil) then
  245.         vOffset = vDefaultOffset
  246.     end
  247.    
  248.     if (flForwardMul == nil) then
  249.         flForwardMul = 16
  250.     end
  251.    
  252.     if (flRightMul == nil) then
  253.         flRightMul = 2
  254.     end
  255.    
  256.     // adjust tracer position for player
  257.     local aEyes = self:ActualEyeAngles()
  258.    
  259.     local vForward = aEyes:Forward()
  260.     vForward:Mul(flForwardMul)
  261.    
  262.     local vRight = aEyes:Right()
  263.     vRight:Mul(flRightMul)
  264.    
  265.     vForward:Add(vSrc)
  266.     vForward:Add(vOffset)
  267.     vForward:Add(vRight)
  268.    
  269.     return vForward
  270. end
  271.  
  272. AMMO_FORCE_DROP_IF_CARRIED = 0x1
  273. AMMO_INTERPRET_PLRDAMAGE_AS_DAMAGE_TO_PLAYER = 0x2
  274.  
  275. FIRE_BULLETS_FIRST_SHOT_ACCURATE = 0x1 // Pop the first shot with perfect accuracy
  276. FIRE_BULLETS_DONT_HIT_UNDERWATER = 0x2 // If the shot hits its target underwater, don't damage it
  277. FIRE_BULLETS_ALLOW_WATER_SURFACE_IMPACTS = 0x4 // If the shot hits water surface, still call DoImpactEffect
  278. -- The engine alerts NPCs by pushing a sound onto a static sound manager
  279. -- However, this cannot be accessed from the Lua state
  280. --FIRE_BULLETS_TEMPORARY_DANGER_SOUND = 0x8 // Danger sounds added from this impact can be stomped immediately if another is queued
  281.  
  282. local ai_debug_shoot_positions = GetConVar("ai_debug_shoot_positions")
  283. local phys_pushscale = GetConVar("phys_pushscale")
  284. local sv_showimpacts = CreateConVar("gs_weapons_showimpacts", "0", bit.bor(FCVAR_REPLICATED, FCVAR_ARCHIVE), "Shows client (red) and server (blue) bullet impact point (1=both, 2=client-only, 3=server-only)")
  285. local sv_showpenetration = CreateConVar("gs_weapons_showpenetration", "0", bit.bor(FCVAR_REPLICATED, FCVAR_ARCHIVE), "Shows penetration trace (if applicable) when the weapon fires")
  286. local sv_showplayerhitboxes = CreateConVar("gs_weapons_showplayerhitboxes", "0", bit.bor(FCVAR_REPLICATED, FCVAR_ARCHIVE), "Show lag compensated hitboxes for the specified player index whenever a player fires.")
  287.  
  288. local nWhizTracer = bit.bor(0x0002, 0x0001)
  289. local iTracerCount = 0 -- Instance global to interact with FireBullets functions
  290.  
  291. local function Splash(vHitPos, bStartedInWater, bEndNotWater, vSrc, pWeapon, bFirstTimePredicted, iAmmoDamageType, iAmmoMinSplash, iAmmoMaxSplash, sSplashEffect)
  292.     local trSplash = bStartedInWater and bEndNotWater and
  293.         util.TraceLine({
  294.             start = vHitPos,
  295.             endpos = vSrc,
  296.             mask = MASK_WATER
  297.         })
  298.     // See if the bullet ended up underwater + started out of the water
  299.     or not (bStartedInWater or bEndNotWater) and
  300.         util.TraceLine({
  301.             start = vSrc,
  302.             endpos = vHitPos,
  303.             mask = MASK_WATER
  304.         })
  305.    
  306.     if (trSplash and not (pWeapon and pWeapon.DoSplashEffect and pWeapon:DoSplashEffect(trSplash)) and bFirstTimePredicted) then
  307.         local data = EffectData()
  308.             data:SetOrigin(trSplash.HitPos)
  309.             data:SetStart(vSrc)
  310.             data:SetNormal(trSplash.HitNormal)
  311.             data:SetSurfaceProp(trSplash.SurfaceProps)
  312.             data:SetDamageType(iAmmoDamageType)
  313.             data:SetHitBox(trSplash.HitBox)
  314.             data:SetEntity(trSplash.Entity)
  315.            
  316.             if (SERVER) then
  317.                 data:SetEntIndex(trSplash.Entity:EntIndex())
  318.             end
  319.            
  320.             data:SetScale(gs.random:RandomFloat(iAmmoMinSplash, iAmmoMaxSplash))
  321.            
  322.             if (bit.band(util.PointContents(trSplash.HitPos), CONTENTS_SLIME) ~= 0) then
  323.                 data:SetFlags(FX_WATER_IN_SLIME)
  324.             end
  325.         util.Effect(sSplashEffect, data) -- FIXME: Add settings to send in custom effects
  326.     end
  327. end
  328.  
  329. local function Impact(Weapon, iAmmoDamageType, bFirstTimePredicted, vSrc, tr, sImpactEffect, sRagdollImpactEffect)
  330.     if (not (Weapon and Weapon.DoImpactEffect and Weapon:DoImpactEffect(tr, iAmmoDamageType))) then
  331.         if (bFirstTimePredicted) then
  332.             local data = EffectData()
  333.                 data:SetOrigin(tr.HitPos)
  334.                 data:SetStart(vSrc)
  335.                 data:SetNormal(tr.HitNormal)
  336.                 data:SetSurfaceProp(tr.SurfaceProps)
  337.                 data:SetDamageType(iAmmoDamageType)
  338.                 data:SetHitBox(tr.HitBox)
  339.                 data:SetEntity(tr.Entity)
  340.                
  341.                 if (SERVER) then
  342.                     data:SetEntIndex(tr.Entity:EntIndex())
  343.                 end
  344.                
  345.             util.Effect(sImpactEffect, data)
  346.         end
  347.     elseif (bFirstTimePredicted) then
  348.         // We may not impact, but we DO need to affect ragdolls on the client
  349.         -- FIXME: Should we?
  350.         local data = EffectData()
  351.             data:SetOrigin(tr.HitPos)
  352.             data:SetStart(vSrc)
  353.             data:SetNormal(tr.HitNormal)
  354.             data:SetSurfaceProp(tr.SurfaceProps)
  355.             data:SetDamageType(iAmmoDamageType)
  356.             data:SetHitBox(tr.HitBox)
  357.             data:SetEntity(tr.Entity)
  358.            
  359.             if (SERVER) then
  360.                 data:SetEntIndex(tr.Entity:EntIndex())
  361.             end
  362.         util.Effect(sRagdollImpactEffect, data)
  363.     end
  364. end
  365.  
  366. local function Damage(bDoDebugHit, bStartedWater, bEndNotWater, iFlags, iDamage, iPlayerDamage, iNPCDamage, iAmmoDamage, pAttacker, pInflictor,
  367.     iAmmoDamageType, tr, Weapon, vShotDir, flAmmoForce, flForce, flPhysPush, iAmmoType, vSrc, fCallback, bFirstTimePredicted, bDrop, sImpactEffect, sRagdollImpactEffect)
  368.    
  369.     local vHitPos = tr.HitPos
  370.     local pEntity = tr.Entity
  371.    
  372.     // draw server impact markers
  373.     if (bDoDebugHit) then
  374.         debugoverlay.Box(vHitPos, vector_debug_min, vector_debug_max, DEBUG_LENGTH, color_debug)
  375.     end
  376.    
  377.     if (not bStartedWater and bEndNotWater or bit.band(iFlags, FIRE_BULLETS_DONT_HIT_UNDERWATER) == 0) then
  378.         -- The engine considers this a float
  379.         -- Even though no values assigned to it are
  380.         -- FIXME: Update these typedefs
  381.         local iActualDamage = iDamage
  382.        
  383.         // If we hit a player, and we have player damage specified, use that instead
  384.         // Adrian: Make sure to use the currect value if we hit a vehicle the player is currently driving.
  385.         -- We don't check for vehicle passengers since GMod has no C++ vehicles with them
  386.         if (pEntity:IsPlayer()) then
  387.             if (iPlayerDamage ~= 0) then
  388.                 iActualDamage = iPlayerDamage
  389.             end
  390.         elseif (pEntity:IsNPC()) then
  391.             if (iNPCDamage ~= 0) then
  392.                 iActualDamage = iNPCDamage
  393.             end
  394.         -- https://github.com/Facepunch/garrysmod-requests/issues/760
  395.         elseif (SERVER and pEntity:IsVehicle()) then
  396.             local pDriver = pEntity:GetDriver()
  397.            
  398.             if (iPlayerDamage ~= 0 and pDriver:IsPlayer()) then
  399.                 iActualDamage = iPlayerDamage
  400.             elseif (iNPCDamage ~= 0 and pDriver:IsNPC()) then
  401.                 iActualDamage = iNPCDamage
  402.             end
  403.         end
  404.        
  405.         if (iActualDamage == 0 and iAmmoDamage ~= 0) then
  406.             iActualDamage = iAmmoDamage
  407.         end
  408.        
  409.         // Damage specified by function parameter
  410.         local info = DamageInfo()
  411.             info:SetAttacker(pAttacker)
  412.             info:SetInflictor(pInflictor)
  413.             info:SetDamage(iActualDamage)
  414.             info:SetDamageType(iAmmoDamageType)
  415.             info:SetDamagePosition(vHitPos)
  416.             info:SetDamageForce(vShotDir * flAmmoForce * flForce * flPhysPush)
  417.             info:SetAmmoType(iAmmoType)
  418.             info:SetReportedPosition(vSrc)
  419.         pEntity:DispatchTraceAttack(info, tr, vShotDir)
  420.        
  421.         if (fCallback) then
  422.             fCallback(pAttacker, tr, info)
  423.         end
  424.        
  425.         if (bEndNotWater or bit.band(iFlags, FIRE_BULLETS_ALLOW_WATER_SURFACE_IMPACTS) ~= 0) then
  426.             Impact(Weapon, iAmmoDamageType, bFirstTimePredicted, vSrc, tr, sImpactEffect, sRagdollImpactEffect)
  427.         end
  428.     end
  429.    
  430.     if (bDrop and SERVER) then
  431.         // Make sure if the player is holding this, he drops it
  432.         DropEntityIfHeld(pEntity)
  433.     end
  434. end
  435.  
  436. local function Tracer(bDebugShoot, vSrc, vFinalHit, bFirstTimePredicted, iTracerFreq, Weapon, self, iAmmoTracerType, iAmmoDamageType, sTracerName)
  437.     if (bDebugShoot) then
  438.         debugoverlay.Line(vSrc, vFinalHit, DEBUG_LENGTH, color_debug)
  439.     end
  440.    
  441.     if (bFirstTimePredicted and iTracerFreq > 0) then
  442.         if (iTracerCount % iTracerFreq == 0) then
  443.             local data = EffectData()
  444.                 data:SetOrigin(vFinalHit)
  445.                
  446.                 if (Weapon) then
  447.                     local iAttachment = Weapon.GetMuzzleAttachment and Weapon:GetMuzzleAttachment() or 1
  448.                     data:SetStart(Weapon.GetTracerOrigin and Weapon:GetTracerOrigin() or self:ComputeTracerStartPosition(vSrc, iAttachment))
  449.                     data:SetAttachment(iAttachment)
  450.                 else
  451.                     data:SetStart(self:ComputeTracerStartPosition(vSrc, 1))
  452.                     data:SetAttachment(1)
  453.                 end
  454.                
  455.                 data:SetDamageType(iAmmoDamageType)
  456.                 data:SetEntity(Weapon or self)
  457.                
  458.                 if (SERVER) then
  459.                     data:SetEntIndex((Weapon or self):EntIndex())
  460.                 end
  461.                
  462.                 data:SetScale(0) -- FIXME: This was in the source code, but I'm pretty sure this is unnecessary + will potentially ruin custom effects
  463.                 data:SetFlags(iAmmoTracerType == TRACER_LINE_AND_WHIZ and nWhizTracer or 0x0002)
  464.             util.Effect(sTracerName, data)
  465.         end
  466.        
  467.         iTracerCount = iTracerCount + 1
  468.     end
  469. end
  470.  
  471. -- Defaults from ammodef.cpp
  472. local tDefaultAmmoTable = {
  473.     name = "",
  474.     plydmg = 0,
  475.     npcdmg = 0,
  476.     maxcarry = 0,
  477.     dmgtype = DMG_GENERIC,
  478.     flags = 0,
  479.     minsplash = 4,
  480.     maxsplash = 8,
  481.     tracer = TRACER_NONE,
  482.     force = 0
  483. }
  484.  
  485. function PLAYER:FireLuaBullets(tInfo)
  486.     if (hook.Run("EntityFireBullets", self, tInfo) == false) then
  487.         return
  488.     end
  489.    
  490.     local bIsPlayer = self:IsPlayer()
  491.    
  492.     if (bIsPlayer) then
  493.         self:LagCompensation(true)
  494.     end
  495.    
  496.     local pWeapon = self:GetActiveWeapon()
  497.     local bWeaponValid = pWeapon:IsValid()
  498.    
  499.     -- FireBullets info
  500.     local iAmmoType
  501.    
  502.     if (tInfo.AmmoType == nil) then
  503.         iAmmoType = -1
  504.     elseif (isstring(tInfo.AmmoType)) then
  505.         iAmmoType = game.GetAmmoID(tInfo.AmmoType)
  506.     else
  507.         iAmmoType = tInfo.AmmoType
  508.     end
  509.    
  510.     local pAttacker = tInfo.Attacker or self
  511.     local fCallback = tInfo.Callback
  512.     local iDamage = tInfo.Damage or 0
  513.     local vDir = tInfo.Dir and tInfo.Dir:GetNormal() or self:GetAimVector()
  514.     local flDistance = tInfo.Distance or MAX_TRACE_LENGTH
  515.     local Filter = tInfo.Filter or self
  516.     local iFlags = tInfo.Flags or 0
  517.     local flForce = tInfo.Force or 1
  518.     local bHullTrace = tInfo.HullTrace
  519.    
  520.     if (bHullTrace == nil) then
  521.         bHullTrace = true
  522.     end
  523.    
  524.     local pInflictor = tInfo.Inflictor and tInfo.Inflictor:IsValid() and tInfo.Inflictor or bWeaponValid and pWeapon or self
  525.     local iMask = tInfo.Mask or MASK_SHOT
  526.     local iNPCDamage = tInfo.NPCDamage or 0
  527.     local iNum = tInfo.Num or 1
  528.     local iPlayerDamage = tInfo.PlayerDamage or 0
  529.     local vSrc = tInfo.Src or self:GetShootPos()
  530.     local iTracerFreq = tInfo.Tracer or 1
  531.     local sTracerName = tInfo.TracerName or "Tracer"
  532.    
  533.     -- Effects
  534.     local sSplashEffect = tInfo.SplashEffect or "gunshotsplash"
  535.     local sImpactEffect = tInfo.ImpactEffect or "Impact"
  536.     local sRagdollImpactEffect = tInfo.RagdollImpactEffect or "RagdollImpact"
  537.    
  538.     -- Ammo
  539.     local tAmmoData = game.GetAmmoData(iAmmoType) or tDefaultAmmoTable
  540.     local iAmmoFlags = tAmmoData.flags
  541.     local flAmmoForce = tAmmoData.force
  542.     local iAmmoDamageType = tAmmoData.dmgtype
  543.     local iAmmoMinSplash = tAmmoData.minsplash
  544.     local iAmmoMaxSplash = tAmmoData.maxsplash
  545.     local iAmmoTracerType = tAmmoData.tracer
  546.    
  547.     local iAmmoNPCDamage = tAmmoData.npcdmg
  548.    
  549.     if (isstring(iAmmoNPCDamage)) then
  550.         iAmmoNPCDamage = GetConVar(iAmmoNPCDamage):GetFloat()
  551.     end
  552.    
  553.     local iAmmoPlayerDamage = tAmmoData.plydmg
  554.    
  555.     if (isstring(iAmmoPlayerDamage)) then
  556.         iAmmoPlayerDamage = GetConVar(iAmmoPlayerDamage):GetFloat()
  557.     end
  558.    
  559.     if (bit.band(iAmmoFlags, AMMO_INTERPRET_PLRDAMAGE_AS_DAMAGE_TO_PLAYER) ~= 0) then
  560.         if (iPlayerDamage == 0) then
  561.             iPlayerDamage = iAmmoPlayerDamage
  562.         end
  563.        
  564.         if (iNPCDamage == 0) then
  565.             iNPCDamage = iAmmoNPCDamage
  566.         end
  567.     end
  568.    
  569.     local iAmmoDamage = bIsPlayer and iAmmoPlayerDamage or iAmmoNPCDamage
  570.    
  571.     -- Loop values
  572.     local bDrop = bit.band(iAmmoFlags, AMMO_FORCE_DROP_IF_CARRIED) ~= 0
  573.     local bDebugShoot = ai_debug_shoot_positions:GetBool()
  574.     local bFirstShotInaccurate = bit.band(iFlags, FIRE_BULLETS_FIRST_SHOT_ACCURATE) == 0
  575.     local flPhysPush = phys_pushscale:GetFloat()
  576.     local bShowPenetration = sv_showpenetration:GetBool()
  577.     local bStartedInWater = bit.band(util.PointContents(vSrc), MASK_WATER) ~= 0
  578.     local bFirstTimePredicted = IsFirstTimePredicted()
  579.     local flSpreadBias, flFlatness, bNegBias, vFireBulletMax, vFireBulletMin, vSpreadRight, vSpreadUp, tEnts, iEntsLen
  580.    
  581.     // Wrap it for network traffic so it's the same between client and server
  582.     local iSeed = self:GetMD5Seed() % 0x100 - 1
  583.    
  584.     -- Don't calculate stuff we won't end up using
  585.     if (bFirstShotInaccurate or iNum ~= 1) then
  586.         flSpreadBias = tInfo.SpreadBias or 0.5
  587.        
  588.         if (flSpreadBias > 1) then
  589.             flSpreadBias = 1
  590.             bNegBias = false
  591.         elseif (flSpreadBias < -1) then
  592.             flSpreadBias = -1
  593.             bNegBias = true
  594.         else
  595.             bNegBias = flSpreadBias < 0
  596.            
  597.             if (bNegBias) then
  598.                 flSpreadBias = -flSpreadBias
  599.             end
  600.         end
  601.        
  602.         local vSpread = tInfo.Spread or vector_origin
  603.         vSpreadRight = vSpread:Right()
  604.         vSpreadRight:Mul(vSpread[1])
  605.         vSpreadUp = vSpread:Up()
  606.         vSpreadUp:Mul(vSpread[2])
  607.        
  608.         if (bHullTrace and iNum ~= 1) then
  609.             local flHullSize = tInfo.HullSize
  610.             vFireBulletMax = flHullSize and Vector(flHullSize, flHullSize, flHullSize) or Vector(3, 3, 3)
  611.             vFireBulletMin = -vFireBulletMax
  612.         end
  613.     end
  614.    
  615.     local bDoDebugHit
  616.    
  617.     do
  618.         //Adrian: visualize server/client player positions
  619.         //This is used to show where the lag compesator thinks the player should be at.
  620.         local iHitNum = sv_showplayerhitboxes:GetInt()
  621.        
  622.         if (iHitNum > 0) then
  623.             local pLagPlayer = Player(iHitNum)
  624.            
  625.             if (pLagPlayer:IsValid()) then
  626.                 pLagPlayer:DrawHitBoxes(DEBUG_LENGTH)
  627.             end
  628.         end
  629.        
  630.         iHitNum = sv_showimpacts:GetInt()
  631.         bDoDebugHit = iHitNum == 1 or (CLIENT and iHitNum == 2) or (SERVER and iHitNum == 3)
  632.     end
  633.    
  634.     for iShot = 1, iNum do
  635.         local vShotDir
  636.         iSeed = iSeed + 1 // use new seed for next bullet
  637.         gs.random:SetSeed(iSeed) // init random system with this seed
  638.        
  639.         // If we're firing multiple shots, and the first shot has to be ba on target, ignore spread
  640.         if (bFirstShotInaccurate or iShot ~= 1) then
  641.             local x
  642.             local y
  643.             local z
  644.  
  645.             repeat
  646.                 x = gs.random:RandomFloat(-flSpreadBias, flSpreadBias) + gs.random:RandomFloat(flSpreadBias - 1, 1 - flSpreadBias)
  647.                 y = gs.random:RandomFloat(-flSpreadBias, flSpreadBias) + gs.random:RandomFloat(flSpreadBias - 1, 1 - flSpreadBias)
  648.  
  649.                 if (bNegBias) then
  650.                     x = x < 0 and -(1 + x) or 1 - x
  651.                     y = y < 0 and -(1 + y) or 1 - y
  652.                 end
  653.  
  654.                 z = x * x + y * y
  655.             until (z <= 1)
  656.  
  657.             vShotDir = vDir + x * vSpreadRight + y * vSpreadUp
  658.             vShotDir:Normalize()
  659.         else
  660.             vShotDir = vDir
  661.         end
  662.        
  663.         local bHitGlass
  664.         local vEnd = vSrc + vShotDir * flDistance
  665.         local vNewSrc = vSrc
  666.         local vFinalHit
  667.        
  668.         repeat
  669.             local tr = bHullTrace and iShot % 2 == 0 and
  670.                 // Half of the shotgun pellets are hulls that make it easier to hit targets with the shotgun.
  671.                 util.TraceHull({
  672.                     start = vNewSrc,
  673.                     endpos = vEnd,
  674.                     mins = vFireBulletMin,
  675.                     maxs = vFireBulletMax,
  676.                     mask = iMask,
  677.                     filter = Filter
  678.                 })
  679.             or
  680.                 util.TraceLine({
  681.                     start = vNewSrc,
  682.                     endpos = vEnd,
  683.                     mask = iMask,
  684.                     filter = Filter
  685.                 })
  686.            
  687.             --[[if (SERVER) then
  688.                 if (bStartedInWater) then
  689.                     local flLengthSqr = vSrc:DistToSqr(tr.HitPos)
  690.                    
  691.                     if (flLengthSqr > SHOT_UNDERWATER_BUBBLE_DIST * SHOT_UNDERWATER_BUBBLE_DIST) then
  692.                         util.BubbleTrail(self:ComputeTracerStartPosition(vSrc),
  693.                         vSrc + SHOT_UNDERWATER_BUBBLE_DIST * vShotDir,
  694.                         WATER_BULLET_BUBBLES_PER_INCH * SHOT_UNDERWATER_BUBBLE_DIST)
  695.                     else
  696.                         local flLength = math.sqrt(flLengthSqr) - 0.1
  697.                         util.BubbleTrail(self:ComputeTracerStartPosition(vSrc),
  698.                         vSrc + flLength * vShotDir,
  699.                         SHOT_UNDERWATER_BUBBLE_DIST * flLength)
  700.                     end
  701.                 end
  702.                
  703.                 // Now hit all triggers along the ray that respond to shots...
  704.                 // Clip the ray to the first collided solid returned from traceline
  705.                 -- https://github.com/Facepunch/garrysmod-requests/issues/755
  706.                 local triggerInfo = DamageInfo()
  707.                     triggerInfo:SetAttacker(pAttacker)
  708.                     triggerInfo:SetInflictor(pAttacker)
  709.                     triggerInfo:SetDamage(iDamage)
  710.                     triggerInfo:SetDamageType(iAmmoDamageType)
  711.                     triggerInfo:CalculateBulletDamageForce(sAmmoType, vShotDir, tr.HitPos, tr.HitPos, flForce)
  712.                     triggerInfo:SetAmmoType(iAmmoType)
  713.                 triggerInfo:TraceAttackToTriggers(triggerInfo, vSrc, tr.HitPos, vShotDir)
  714.             end]]
  715.            
  716.             local vHitPos = tr.HitPos
  717.             vFinalHit = vHitPos
  718.            
  719.             local bEndNotWater = bit.band(util.PointContents(vHitPos), MASK_WATER) == 0
  720.             Splash(vHitPos, bStartedInWater, bEndNotWater, vSrc, bWeaponValid and pWeapon, bFirstTimePredicted, iAmmoDamageType, iAmmoMinSplash, iAmmoMaxSplash, sSplashEffect)
  721.            
  722.             if (not tr.Hit or tr.HitSky) then
  723.                 break // we didn't hit anything, stop tracing shoot
  724.             end
  725.            
  726.             Damage(bDoDebugHit, bStartedWater, bEndNotWater, iFlags, iDamage, iPlayerDamage, iNPCDamage, iAmmoDamage, pAttacker, pInflictor, iAmmoDamageType,
  727.                 tr, bWeaponValid and pWeapon, vShotDir, flAmmoForce, flForce, flPhysPush, iAmmoType, vSrc, fCallback, bFirstTimePredicted, bDrop, sImpactEffect, sRagdollImpactEffect)
  728.            
  729.             // do damage, paint decals
  730.             -- https://github.com/Facepunch/garrysmod-issues/issues/2741
  731.             local pEntity = tr.Entity
  732.             bHitGlass = --[[tr.MatType == MAT_GLASS]] pEntity:IsBreakable() and not pEntity:HasSpawnFlags(SF_BREAK_NO_BULLET_PENETRATION)
  733.            
  734.             // See if we hit glass
  735.             // Query the func_breakable for whether it wants to allow for bullet penetration
  736.             if (bHitGlass) then
  737.                 if (tEnts == nil) then
  738.                     tEnts = ents.GetAll()
  739.                     iEntsLen = #tEnts
  740.                 end
  741.                
  742.                 local bReplace = false
  743.                
  744.                 -- Trace for only the entity we hit
  745.                 for i = iEntsLen, 1, -1 do
  746.                     if (tEnts[i] == pEntity) then
  747.                         tEnts[i] = tEnts[iEntsLen]
  748.                         tEnts[iEntsLen] = nil
  749.                         bReplace = true
  750.                        
  751.                         break
  752.                     end
  753.                 end
  754.                
  755.                 util.TraceLine({
  756.                     start = vEnd,
  757.                     endpos = vHitPos,
  758.                     mask = iMask,
  759.                     filter = tEnts,
  760.                     ignoreworld = true,
  761.                     output = tr
  762.                 })
  763.                
  764.                 // bullet did penetrate object, exit Decal
  765.                 Impact(bWeaponValid and pWeapon, iAmmoDamageType, bFirstTimePredicted, vSrc, tr, sImpactEffect, sRagdollImpactEffect)
  766.                
  767.                 vNewSrc = tr.HitPos
  768.                
  769.                 if (bShowPenetration) then
  770.                     debugoverlay.Line(vHitPos, vNewSrc, DEBUG_LENGTH, color_altdebug)
  771.                 end
  772.                
  773.                 if (bDoDebugHit) then
  774.                     debugoverlay.Box(vNewSrc, vector_debug_min, vector_debug_max, DEBUG_LENGTH, color_altdebug)
  775.                 end
  776.                
  777.                 -- Should never be false
  778.                 if (bReplace) then
  779.                     tEnts[iEntsLen] = pEntity
  780.                 end
  781.             end
  782.         until (not bHitGlass)
  783.        
  784.         Tracer(bDebugShoot, vSrc, vFinalHit, bFirstTimePredicted, iTracerFreq, bWeaponValid and pWeapon, self, iAmmoTracerType, iAmmoDamageType, sTracerName)
  785.     end
  786.    
  787.     if (bIsPlayer) then
  788.         self:LagCompensation(false)
  789.     end
  790. end
  791.  
  792. local tMaterialParameters = {
  793.     [MAT_METAL] = {
  794.         Penetration = 0.5,
  795.         Damage = 0.3
  796.     },
  797.     [MAT_DIRT] = {
  798.         Penetration = 0.5,
  799.         Damage = 0.3
  800.     },
  801.     [MAT_CONCRETE] = {
  802.         Penetration = 0.4,
  803.         Damage = 0.25
  804.     },
  805.     [MAT_GRATE] = {
  806.         Penetration = 1,
  807.         Damage = 0.99
  808.     },
  809.     [MAT_VENT] = {
  810.         Penetration = 0.5,
  811.         Damage = 0.45
  812.     },
  813.     [MAT_TILE] = {
  814.         Penetration = 0.65,
  815.         Damage = 0.3
  816.     },
  817.     [MAT_COMPUTER] = {
  818.         Penetration = 0.4,
  819.         Damage = 0.45
  820.     },
  821.     [MAT_WOOD] = {
  822.         Penetration = 1,
  823.         Damage = 0.6
  824.     },
  825.     [MAT_GLASS] = {
  826.         Penetration = 1,
  827.         Damage = 0.99
  828.     }
  829. }
  830.  
  831. local tDoublePenetration = {
  832.     [MAT_WOOD] = true,
  833.     [MAT_METAL] = true,
  834.     [MAT_GRATE] = true,
  835.     [MAT_GLASS] = true
  836. }
  837.  
  838. local MASK_HITBOX = bit.bor(MASK_SOLID, CONTENTS_DEBRIS, CONTENTS_HITBOX)
  839.  
  840. function PLAYER:FireCSSBullets(tInfo)
  841.     if (hook.Run("EntityFireBullets", self, tInfo) == false) then
  842.         return
  843.     end
  844.    
  845.     local bIsPlayer = self:IsPlayer()
  846.    
  847.     if (bIsPlayer) then
  848.         self:LagCompensation(true)
  849.     end
  850.    
  851.     local pWeapon = self:GetActiveWeapon()
  852.     local bWeaponValid = pWeapon:IsValid()
  853.    
  854.     -- FireCSSBullets info
  855.     local iAmmoType
  856.    
  857.     if (tInfo.AmmoType == nil) then
  858.         iAmmoType = -1
  859.     elseif (isstring(tInfo.AmmoType)) then
  860.         iAmmoType = game.GetAmmoID(tInfo.AmmoType)
  861.     else
  862.         iAmmoType = tInfo.AmmoType
  863.     end
  864.    
  865.     local pAttacker = tInfo.Attacker and tInfo.Attacker:IsValid() and tInfo.Attacker or self
  866.     local fCallback = tInfo.Callback
  867.     local iDamage = tInfo.Damage or 0
  868.     local flDecayRate = tInfo.DecayRate or 1/500
  869.     local vDir = tInfo.Dir and tInfo.Dir:GetNormal() or self:GetAimVector()
  870.     local flDistance = tInfo.Distance or MAX_TRACE_LENGTH
  871.     local flExitMaxDistance = tInfo.ExitMaxDistance or 128
  872.     local flExitStepSize = tInfo.ExitStepSize or 24
  873.    
  874.     local bFilterIsFunction
  875.     local iFilterEnd
  876.     local Filter = tInfo.Filter
  877.    
  878.     -- Yes, this is dirty
  879.     -- But this prevents tables from being created when it's not necessary
  880.     -- Also supports functional filters
  881.     if (isentity(Filter)) then
  882.         iFilterEnd = -1
  883.         bFilterIsFunction = false
  884.     elseif (istable(Filter)) then
  885.         -- Length of the table will be found if penetration happens
  886.         --iFilterEnd = #Filter
  887.         bFilterIsFunction = false
  888.     elseif (isfunction(Filter)) then
  889.         bFilterIsFunction = true
  890.     else
  891.         Filter = self
  892.         iFilterEnd = -1
  893.         bFilterIsFunction = false
  894.     end
  895.    
  896.     local iFlags = tInfo.Flags or 0
  897.     local flForce = tInfo.Force or 1
  898.     --local flHitboxTolerance = tInfo.HitboxTolerance or 40
  899.     local bHullTrace = tInfo.HullTrace or false
  900.     local pInflictor = tInfo.Inflictor and tInfo.Inflictor:IsValid() and tInfo.Inflictor or bWeaponValid and pWeapon or self
  901.     local iMask = tInfo.Mask or MASK_HITBOX
  902.     local iNPCDamage = tInfo.NPCDamage or 0
  903.     local iNum = tInfo.Num or 1
  904.     local iPenetration = tInfo.Penetration or 0
  905.     local iPlayerDamage = tInfo.PlayerDamage or 0
  906.     local flRangeModifier = tInfo.RangeModifier or 1
  907.     local vSrc = tInfo.Src or self:GetShootPos()
  908.     local iTracerFreq = tInfo.Tracer or 1
  909.     local sTracerName = tInfo.TracerName or "Tracer"
  910.    
  911.     -- Effects
  912.     local sSplashEffect = tInfo.SplashEffect or "gunshotsplash"
  913.     local sImpactEffect = tInfo.ImpactEffect or "Impact"
  914.     local sRagdollImpactEffect = tInfo.RagdollImpactEffect or "RagdollImpact"
  915.    
  916.     -- Ammo
  917.     local tAmmoData = game.GetAmmoData(iAmmoType) or tDefaultAmmoTable
  918.     local iAmmoFlags = tAmmoData.flags
  919.     local flAmmoForce = tAmmoData.force
  920.     local iAmmoDamageType = tAmmoData.dmgtype
  921.     local iAmmoMinSplash = tAmmoData.minsplash
  922.     local iAmmoMaxSplash = tAmmoData.maxsplash
  923.     local iAmmoTracerType = tAmmoData.tracer
  924.    
  925.     local iAmmoNPCDamage = tAmmoData.npcdmg
  926.    
  927.     if (isstring(iAmmoNPCDamage)) then
  928.         iAmmoNPCDamage = GetConVar(iAmmoNPCDamage):GetFloat()
  929.     end
  930.    
  931.     local iAmmoPlayerDamage = tAmmoData.plydmg
  932.    
  933.     if (isstring(iAmmoPlayerDamage)) then
  934.         iAmmoPlayerDamage = GetConVar(iAmmoPlayerDamage):GetFloat()
  935.     end
  936.    
  937.     -- FIXME: These should be tied to ammo types
  938.     local flPenetrationDistance = tInfo.PenetrationDistance or 0
  939.     local flPenetrationPower = tInfo.PenetrationPower or 0
  940.    
  941.     -- Loop values
  942.     local bDrop = bit.band(iAmmoFlags, AMMO_FORCE_DROP_IF_CARRIED) ~= 0
  943.     local bDebugShoot = ai_debug_shoot_positions:GetBool()
  944.     local bFirstShotInaccurate = bit.band(iFlags, FIRE_BULLETS_FIRST_SHOT_ACCURATE) == 0
  945.     local flPhysPush = phys_pushscale:GetFloat()
  946.     local bShowPenetration = sv_showpenetration:GetBool()
  947.     local bStartedInWater = bit.band(util.PointContents(vSrc), MASK_WATER) ~= 0
  948.     local bFirstTimePredicted = IsFirstTimePredicted()
  949.     local flSpreadBias, vShootRight, vShootUp, vFireBulletMin, vFireBulletMax, tEnts, iEntsLen
  950.    
  951.     // Wrap it for network traffic so it's the same between client and server
  952.     local iSeed = self:GetMD5Seed() % 0x100
  953.    
  954.     -- Don't calculate stuff we won't end up using
  955.     if (bFirstShotInaccurate or iNum ~= 1) then
  956.         local vSpread = tInfo.Spread or vector_origin
  957.         flSpreadBias = tInfo.SpreadBias or 0.5
  958.         vShootRight = vDir:Right()
  959.         vShootRight:Mul(vSpread[1])
  960.         vShootUp = vDir:Up()
  961.         vShootUp:Mul(vSpread[2])
  962.        
  963.         if (bHullTrace and iNum ~= 1) then
  964.             local flHullSize = tInfo.HullSize
  965.             vFireBulletMax = flHullSize and Vector(flHullSize, flHullSize, flHullSize) or Vector(3, 3, 3)
  966.             vFireBulletMin = -vFireBulletMax
  967.         end
  968.     end
  969.    
  970.     local bDoDebugHit
  971.    
  972.     do
  973.         //Adrian: visualize server/client player positions
  974.         //This is used to show where the lag compesator thinks the player should be at.
  975.         local iHitNum = sv_showplayerhitboxes:GetInt()
  976.        
  977.         if (iHitNum > 0) then
  978.             local pLagPlayer = Player(iHitNum)
  979.            
  980.             if (pLagPlayer:IsValid()) then
  981.                 pLagPlayer:DrawHitBoxes(DEBUG_LENGTH)
  982.             end
  983.         end
  984.        
  985.         iHitNum = sv_showimpacts:GetInt()
  986.         bDoDebugHit = iHitNum == 1 or (CLIENT and iHitNum == 2) or (SERVER and iHitNum == 3)
  987.     end
  988.    
  989.     for iShot = 1, iNum do
  990.         local vShotDir
  991.         iSeed = iSeed + 1 // use new seed for next bullet
  992.         gs.random:SetSeed(iSeed) // init random system with this seed
  993.        
  994.         -- Loop values
  995.         local flCurrentDamage = iDamage // damage of the bullet at it's current trajectory
  996.         local flCurrentPlayerDamage = iPlayerDamage
  997.         local flCurrentNPCDamage = iNPCDamage
  998.         local flCurrentDistance = 0 // distance that the bullet has traveled so far
  999.         local vNewSrc = vSrc
  1000.         local vFinalHit
  1001.        
  1002.         // add the spray
  1003.         if (bFirstShotInaccurate or iShot ~= 1) then
  1004.             vShotDir = vDir + vShootRight * (gs.random:RandomFloat(-flSpreadBias, flSpreadBias) + gs.random:RandomFloat(-flSpreadBias, flSpreadBias))
  1005.             + vShootUp * (gs.random:RandomFloat(-flSpreadBias, flSpreadBias) + gs.random:RandomFloat(-flSpreadBias, flSpreadBias))
  1006.             vShotDir:Normalize()
  1007.         else
  1008.             vShotDir = vDir
  1009.         end
  1010.        
  1011.         local vEnd = vNewSrc + vShotDir * flDistance
  1012.        
  1013.         repeat
  1014.             local tr = bHullTrace and iShot % 2 == 0 and
  1015.                 // Half of the shotgun pellets are hulls that make it easier to hit targets with the shotgun.
  1016.                 util.TraceHull({
  1017.                     start = vNewSrc,
  1018.                     endpos = vEnd,
  1019.                     mins = vFireBulletMin,
  1020.                     maxs = vFireBulletMax,
  1021.                     mask = iMask,
  1022.                     filter = Filter
  1023.                 })
  1024.             or
  1025.                 util.TraceLine({
  1026.                     start = vNewSrc,
  1027.                     endpos = vEnd,
  1028.                     mask = iMask,
  1029.                     filter = Filter
  1030.                 })
  1031.            
  1032.             // Check for player hitboxes extending outside their collision bounds
  1033.             --util.ClipTraceToPlayers(tr, vNewSrc, vEnd + vShotDir * flHitboxTolerance, Filter, iMask)
  1034.            
  1035.             local vHitPos = tr.HitPos
  1036.             vFinalHit = vHitPos
  1037.            
  1038.             local bEndNotWater = bit.band(util.PointContents(vHitPos), MASK_WATER) == 0
  1039.             Splash(vHitPos, bStartedInWater, bEndNotWater, vSrc, bWeaponValid and pWeapon, bFirstTimePredicted, iAmmoDamageType, iAmmoMinSplash, iAmmoMaxSplash, sSplashEffect)
  1040.            
  1041.             if (not tr.Hit or tr.HitSky) then
  1042.                 break // we didn't hit anything, stop tracing shoot
  1043.             end
  1044.            
  1045.             /************* MATERIAL DETECTION ***********/
  1046.             -- https://github.com/Facepunch/garrysmod-requests/issues/923
  1047.             local iEnterMaterial = tr.MatType
  1048.            
  1049.             -- https://github.com/Facepunch/garrysmod-requests/issues/787
  1050.             // since some railings in de_inferno are CONTENTS_GRATE but CHAR_TEX_CONCRETE, we'll trust the
  1051.             // CONTENTS_GRATE and use a high damage modifier.
  1052.             // If we're a concrete grate (TOOLS/TOOLSINVISIBLE texture) allow more penetrating power.
  1053.             local bHitGrate = iEnterMaterial == MAT_GRATE or bit.band(util.PointContents(vHitPos), CONTENTS_GRATE) ~= 0
  1054.            
  1055.             // calculate the damage based on the distance the bullet travelled.
  1056.             flCurrentDistance = flCurrentDistance + tr.Fraction * flDistance
  1057.             local flDecay = flRangeModifier ^ (flCurrentDistance * flDecayRate)
  1058.             flCurrentDamage = flCurrentDamage * flDecay
  1059.             flCurrentPlayerDamage = flCurrentPlayerDamage * flDecay
  1060.             flCurrentNPCDamage = flCurrentNPCDamage * flDecay
  1061.            
  1062.             Damage(bDoDebugHit, bStartedWater, bEndNotWater, iFlags, flCurrentDamage, flCurrentPlayerDamage, flCurrentNPCDamage, bIsPlayer and iAmmoPlayerDamage or iAmmoNPCDamage, pAttacker,
  1063.                 pInflictor, iAmmoDamageType, tr, bWeaponValid and pWeapon, vShotDir, flAmmoForce, flForce, flPhysPush, iAmmoType, vSrc, fCallback, bFirstTimePredicted, bDrop, sImpactEffect, sRagdollImpactEffect)
  1064.            
  1065.             // check if we reach penetration distance, no more penetrations after that
  1066.             if (flCurrentDistance > flPenetrationDistance and iPenetration > 0) then
  1067.                 iPenetration = 0
  1068.             end
  1069.            
  1070.             // check if bullet can penetrate another entity
  1071.             // If we hit a grate with iPenetration == 0, stop on the next thing we hit
  1072.             if (iPenetration == 0 and not bHitGrate or iPenetration < 0) then
  1073.                 break
  1074.             end
  1075.            
  1076.             local pEntity = tr.Entity
  1077.            
  1078.             if (pEntity:IsBreakable() and pEntity:HasSpawnFlags(SF_BREAK_NO_BULLET_PENETRATION)) then
  1079.                 break // no, stop
  1080.             end
  1081.            
  1082.             if (tEnts == nil) then
  1083.                 tEnts = ents.GetAll()
  1084.                 iEntsLen = #tEnts
  1085.             end
  1086.            
  1087.             if (pEntity:IsWorld()) then
  1088.                 local flExitDistance = 0
  1089.                
  1090.                 local tr = tr
  1091.                 local tTrace = {
  1092.                     mask = iMask,
  1093.                     filter = tEnts,
  1094.                     output = tr
  1095.                 }
  1096.                
  1097.                 // try to penetrate object, maximum penetration is 128 inch
  1098.                 while (flExitDistance < flExitMaxDistance) do
  1099.                     flExitDistance = math.min(flExitMaxDistance, flExitDistance + flExitStepSize)
  1100.                    
  1101.                     local vHit = vHitPos + flExitDistance * vShotDir
  1102.                     tTrace.start = vHit
  1103.                     tTrace.endpos = vHit
  1104.                     util.TraceLine(tTrace)
  1105.                    
  1106.                     if (not tr.Hit) then
  1107.                         // found first free point
  1108.                         goto PositionFound
  1109.                     end
  1110.                 end
  1111.                
  1112.                 -- Nowhere to penetrate
  1113.                 do break end
  1114.                
  1115.                 ::PositionFound::
  1116.                
  1117.                 tTrace.endpos = vHitPos
  1118.                 util.TraceLine(tTrace)
  1119.             else
  1120.                 local bReplace = false
  1121.                
  1122.                 -- Trace for only the entity we hit
  1123.                 for i = iEntsLen, 1, -1 do
  1124.                     if (tEnts[i] == pEntity) then
  1125.                         tEnts[i] = tEnts[iEntsLen]
  1126.                         tEnts[iEntsLen] = nil
  1127.                         bReplace = true
  1128.                        
  1129.                         break
  1130.                     end
  1131.                 end
  1132.                
  1133.                 util.TraceLine({
  1134.                     start = vEnd,
  1135.                     endpos = vHitPos,
  1136.                     mask = iMask,
  1137.                     filter = tEnts,
  1138.                     ignoreworld = true,
  1139.                     output = tr
  1140.                 })
  1141.                
  1142.                 -- Should never be false
  1143.                 if (bReplace) then
  1144.                     tEnts[iEntsLen] = pEntity
  1145.                 end
  1146.             end
  1147.            
  1148.             vNewSrc = tr.HitPos
  1149.             vEnd = vNewSrc + vShotDir * flDistance
  1150.            
  1151.             if (bShowPenetration) then
  1152.                 debugoverlay.Line(vHitPos, vNewSrc, DEBUG_LENGTH, color_altdebug)
  1153.             end
  1154.            
  1155.             local iExitMaterial = tr.MatType
  1156.             local tMatParams = tMaterialParameters[iEnterMaterial]
  1157.             local flPenetrationModifier = bHitGrate and 1 or tMatParams and tMatParams.Penetration or 1
  1158.             local flDamageModifier = bHitGrate and 0.99 or tMatParams and tMatParams.Damage or 0.5
  1159.             local flTraceDistance = (vNewSrc - vHitPos):LengthSqr()
  1160.            
  1161.             // if enter & exit point is wood or metal we assume this is
  1162.             // a hollow crate or barrel and give a penetration bonus
  1163.             if (bHitGrate and (iExitMaterial == MAT_GRATE or bit.band(util.PointContents(tr.HitPos), CONTENTS_GRATE) ~= 0) or iEnterMaterial == iExitMaterial and tDoublePenetration[iExitMaterial]) then
  1164.                 flPenetrationModifier = flPenetrationModifier * 2  
  1165.             end
  1166.  
  1167.             local flPenetrationDistance = flPenetrationPower * flPenetrationModifier
  1168.            
  1169.             // check if bullet has enough power to penetrate this distance for this material
  1170.             if (flTraceDistance > flPenetrationDistance * flPenetrationDistance) then
  1171.                 break // bullet hasn't enough power to penetrate this distance
  1172.             end
  1173.            
  1174.             if (bDoDebugHit) then
  1175.                 debugoverlay.Box(tr.HitPos, vector_debug_min, vector_debug_max, DEBUG_LENGTH, color_altdebug)
  1176.             end
  1177.            
  1178.             // bullet did penetrate object, exit Decal
  1179.             Impact(bWeaponValid and pWeapon, iAmmoDamageType, bFirstTimePredicted, vSrc, tr, sImpactEffect, sRagdollImpactEffect)
  1180.            
  1181.             // penetration was successful
  1182.             flTraceDistance = math.sqrt(flTraceDistance)
  1183.            
  1184.             // setup new start end parameters for successive trace
  1185.             flPenetrationPower = flPenetrationPower - flTraceDistance / flPenetrationModifier
  1186.             flCurrentDistance = flCurrentDistance + flTraceDistance
  1187.            
  1188.             // reduce damage power each time we hit something other than a grate
  1189.             flCurrentDamage = flCurrentDamage * flDamageModifier
  1190.             flDistance = (flDistance - flCurrentDistance) * 0.5
  1191.            
  1192.             // reduce penetration counter
  1193.             iPenetration = iPenetration - 1
  1194.            
  1195.             -- Can't hit players more than once
  1196.             if (pEntity:IsPlayer() or pEntity:IsNPC()) then
  1197.                 if (bFilterIsFunction) then
  1198.                     local fOldFilter = Filter
  1199.                     Filter = function(pTest)
  1200.                         return fOldFilter(pTest) and pTest ~= pEntity
  1201.                     end
  1202.                 elseif (iFilterEnd == -1) then
  1203.                     Filter = {Filter, pEntity}
  1204.                     iFilterEnd = 2
  1205.                 else
  1206.                     iFilterEnd = (iFilterEnd or #Filter) + 1
  1207.                     Filter[iFilterEnd] = pEntity
  1208.                 end
  1209.             end
  1210.         until (flCurrentDamage <= 0)
  1211.        
  1212.         Tracer(bDebugShoot, vSrc, vFinalHit, bFirstTimePredicted, iTracerFreq, bWeaponValid and pWeapon, self, iAmmoTracerType, iAmmoDamageType, sTracerName)
  1213.     end
  1214.    
  1215.     if (bIsPlayer) then
  1216.         self:LagCompensation(false)
  1217.     end
  1218. end
Add Comment
Please, Sign In to add comment