mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-16 21:33:46 +03:00
1151 lines
36 KiB
Lua
1151 lines
36 KiB
Lua
--[[
|
|
| 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/
|
|
--]]
|
|
|
|
|
|
-- Copyright (c) 2018-2020 TFA Base Devs
|
|
|
|
-- Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
-- of this software and associated documentation files (the "Software"), to deal
|
|
-- in the Software without restriction, including without limitation the rights
|
|
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
-- copies of the Software, and to permit persons to whom the Software is
|
|
-- furnished to do so, subject to the following conditions:
|
|
|
|
-- The above copyright notice and this permission notice shall be included in all
|
|
-- copies or substantial portions of the Software.
|
|
|
|
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
-- SOFTWARE.
|
|
|
|
local vector_origin = Vector()
|
|
local angle_zero = Angle()
|
|
|
|
local l_mathClamp = math.Clamp
|
|
local Lerp = Lerp
|
|
SWEP.MainBullet = {}
|
|
SWEP.MainBullet.Spread = Vector()
|
|
|
|
local function DisableOwnerDamage(a, b, c)
|
|
if b.Entity == a and c then
|
|
c:ScaleDamage(0)
|
|
end
|
|
end
|
|
|
|
local ballistics_distcv = GetConVar("sv_tfa_ballistics_mindist")
|
|
|
|
local function BallisticFirebullet(ply, bul, ovr, angPreserve)
|
|
local wep = ply:GetActiveWeapon()
|
|
|
|
if TFA.Ballistics and TFA.Ballistics:ShouldUse(wep) then
|
|
if ballistics_distcv:GetInt() == -1 or util.QuickTrace(ply:GetShootPos(), ply:GetAimVector() * 0x7fff, ply).HitPos:Distance(ply:GetShootPos()) > (ballistics_distcv:GetFloat() * TFA.Ballistics.UnitScale) then
|
|
bul.SmokeParticle = bul.SmokeParticle or wep.BulletTracer or wep.TracerBallistic or wep.BallisticTracer or wep.BallisticsTracer
|
|
|
|
if ovr then
|
|
TFA.Ballistics:FireBullets(wep, bul, angPreserve or angle_zero, true)
|
|
else
|
|
TFA.Ballistics:FireBullets(wep, bul, angPreserve)
|
|
end
|
|
else
|
|
ply:FireBullets(bul)
|
|
end
|
|
else
|
|
ply:FireBullets(bul)
|
|
end
|
|
end
|
|
|
|
--[[
|
|
Function Name: ShootBulletInformation
|
|
Syntax: self:ShootBulletInformation().
|
|
Returns: Nothing.
|
|
Notes: Used to generate a self.MainBullet table which is then sent to self:ShootBullet, and also to call shooteffects.
|
|
Purpose: Bullet
|
|
]]
|
|
--
|
|
local sv_tfa_damage_multiplier = GetConVar("sv_tfa_damage_multiplier")
|
|
local sv_tfa_damage_multiplier_npc = GetConVar("sv_tfa_damage_multiplier_npc")
|
|
local cv_dmg_mult_min = GetConVar("sv_tfa_damage_mult_min")
|
|
local cv_dmg_mult_max = GetConVar("sv_tfa_damage_mult_max")
|
|
local sv_tfa_recoil_legacy = GetConVar("sv_tfa_recoil_legacy")
|
|
local dmg, con, rec
|
|
|
|
function SWEP:ShootBulletInformation()
|
|
--self:CalculateRatios()
|
|
self:UpdateConDamage()
|
|
|
|
self.lastbul = nil
|
|
self.lastbulnoric = false
|
|
self.ConDamageMultiplier = self:GetOwner():IsNPC() and sv_tfa_damage_multiplier_npc:GetFloat() or sv_tfa_damage_multiplier:GetFloat()
|
|
|
|
if not IsFirstTimePredicted() then return end
|
|
|
|
con, rec = self:CalculateConeRecoil()
|
|
|
|
local tmpranddamage = math.Rand(cv_dmg_mult_min:GetFloat(), cv_dmg_mult_max:GetFloat())
|
|
local basedamage = self.ConDamageMultiplier * self:GetStatL("Primary.Damage")
|
|
dmg = basedamage * tmpranddamage
|
|
|
|
local ns = self:GetStatL("Primary.NumShots")
|
|
local clip = (self:GetStatL("Primary.ClipSize") == -1) and self:Ammo1() or self:Clip1()
|
|
|
|
ns = math.Round(ns, math.min(clip / self:GetStatL("Primary.NumShots"), 1))
|
|
|
|
self:ShootBullet(dmg, rec, ns, con)
|
|
end
|
|
|
|
function SWEP:PreSpawnProjectile(ent)
|
|
-- override
|
|
end
|
|
|
|
function SWEP:PostSpawnProjectile(ent)
|
|
-- override
|
|
end
|
|
|
|
--[[
|
|
Function Name: ShootBullet
|
|
Syntax: self:ShootBullet(damage, recoil, number of bullets, spray cone, disable ricochet, override the generated self.MainBullet table with this value if you send it).
|
|
Returns: Nothing.
|
|
Notes: Used to shoot a self.MainBullet.
|
|
Purpose: Bullet
|
|
]]
|
|
--
|
|
local TracerName
|
|
local cv_forcemult = GetConVar("sv_tfa_force_multiplier")
|
|
local sv_tfa_bullet_penetration_power_mul = GetConVar("sv_tfa_bullet_penetration_power_mul")
|
|
local sv_tfa_bullet_randomseed = GetConVar("sv_tfa_bullet_randomseed")
|
|
|
|
local randomseed = "tfa_" .. tostring({})
|
|
|
|
SWEP.Primary.SpreadPattern = ""
|
|
SWEP.Primary.SpreadBiasYaw = 1
|
|
SWEP.Primary.SpreadBiasPitch = 1
|
|
|
|
-- Default ComputeBulletDeviation implementation
|
|
-- Custom implementations should return two numbers
|
|
-- Yaw (X) and Pitch (Y) deviation
|
|
function SWEP:ComputeBulletDeviation(bulletNum, totalBullets, aimcone)
|
|
local sharedRandomSeed
|
|
|
|
if sv_tfa_bullet_randomseed:GetBool() then
|
|
sharedRandomSeed = randomseed .. CurTime()
|
|
else
|
|
sharedRandomSeed = "TFA_ShootBullet" .. CurTime()
|
|
end
|
|
|
|
return
|
|
-- Yaw
|
|
util.SharedRandom(sharedRandomSeed, -aimcone * 45 * self:GetStatL("Primary.SpreadBiasYaw"), aimcone * 45 * self:GetStatL("Primary.SpreadBiasYaw"), totalBullets + 1 + bulletNum),
|
|
-- Pitch
|
|
util.SharedRandom(sharedRandomSeed, -aimcone * 45 * self:GetStatL("Primary.SpreadBiasPitch"), aimcone * 45 * self:GetStatL("Primary.SpreadBiasPitch"), bulletNum)
|
|
end
|
|
|
|
function SWEP:ShootBullet(damage, recoil, num_bullets, aimcone, disablericochet, bulletoverride)
|
|
if not IsFirstTimePredicted() and not game.SinglePlayer() then return end
|
|
num_bullets = num_bullets or 1
|
|
aimcone = aimcone or 0
|
|
|
|
self:SetLastGunFire(CurTime())
|
|
|
|
if self:GetStatL("Primary.Projectile") then
|
|
if CLIENT then return end
|
|
|
|
for _ = 1, num_bullets do
|
|
local ent = ents.Create(self:GetStatL("Primary.Projectile"))
|
|
|
|
local ang = self:GetOwner():GetAimVector():Angle()
|
|
|
|
if not sv_tfa_recoil_legacy:GetBool() then
|
|
ang.p = ang.p + self:GetViewPunchP()
|
|
ang.y = ang.y + self:GetViewPunchY()
|
|
end
|
|
|
|
ang:RotateAroundAxis(ang:Right(), -aimcone / 2 + math.Rand(0, aimcone))
|
|
ang:RotateAroundAxis(ang:Up(), -aimcone / 2 + math.Rand(0, aimcone))
|
|
|
|
ent:SetPos(self:GetOwner():GetShootPos())
|
|
ent:SetOwner(self:GetOwner())
|
|
ent:SetAngles(ang)
|
|
ent.damage = self:GetStatL("Primary.Damage")
|
|
ent.mydamage = self:GetStatL("Primary.Damage")
|
|
|
|
if self:GetStatL("Primary.ProjectileModel") then
|
|
ent:SetModel(self:GetStatL("Primary.ProjectileModel"))
|
|
end
|
|
|
|
self:PreSpawnProjectile(ent)
|
|
|
|
ent:Spawn()
|
|
|
|
local dir = ang:Forward()
|
|
dir:Mul(self:GetStatL("Primary.ProjectileVelocity"))
|
|
|
|
ent:SetVelocity(dir)
|
|
local phys = ent:GetPhysicsObject()
|
|
|
|
if IsValid(phys) then
|
|
phys:SetVelocity(dir)
|
|
end
|
|
|
|
if self.ProjectileModel then
|
|
ent:SetModel(self:GetStatL("Primary.ProjectileModel"))
|
|
end
|
|
|
|
ent:SetOwner(self:GetOwner())
|
|
|
|
self:PostSpawnProjectile(ent)
|
|
end
|
|
-- Source
|
|
-- Dir of self.MainBullet
|
|
-- Aim Cone X
|
|
-- Aim Cone Y
|
|
-- Show a tracer on every x bullets
|
|
-- Amount of force to give to phys objects
|
|
|
|
return
|
|
end
|
|
|
|
if self.Tracer == 1 then
|
|
TracerName = "Ar2Tracer"
|
|
elseif self.Tracer == 2 then
|
|
TracerName = "AirboatGunHeavyTracer"
|
|
else
|
|
TracerName = "Tracer"
|
|
end
|
|
|
|
self.MainBullet.PCFTracer = nil
|
|
|
|
if self:GetStatL("TracerName") and self:GetStatL("TracerName") ~= "" then
|
|
if self:GetStatL("TracerPCF") then
|
|
TracerName = nil
|
|
self.MainBullet.PCFTracer = self:GetStatL("TracerName")
|
|
self.MainBullet.Tracer = 0
|
|
else
|
|
TracerName = self:GetStatL("TracerName")
|
|
end
|
|
end
|
|
|
|
local owner = self:GetOwner()
|
|
|
|
self.MainBullet.Attacker = owner
|
|
self.MainBullet.Inflictor = self
|
|
self.MainBullet.Src = owner:GetShootPos()
|
|
|
|
self.MainBullet.Dir = self:GetAimVector()
|
|
self.MainBullet.HullSize = self:GetStatL("Primary.HullSize") or 0
|
|
self.MainBullet.Spread.x = 0
|
|
self.MainBullet.Spread.y = 0
|
|
|
|
self.MainBullet.Num = 1
|
|
|
|
if num_bullets == 1 then
|
|
local dYaw, dPitch = self:ComputeBulletDeviation(1, 1, aimcone)
|
|
|
|
local ang = self.MainBullet.Dir:Angle()
|
|
local up, right = ang:Up(), ang:Right()
|
|
|
|
ang:RotateAroundAxis(up, dYaw)
|
|
ang:RotateAroundAxis(right, dPitch)
|
|
|
|
self.MainBullet.Dir = ang:Forward()
|
|
end
|
|
|
|
self.MainBullet.Wep = self
|
|
|
|
if self.TracerPCF then
|
|
self.MainBullet.Tracer = 0
|
|
else
|
|
self.MainBullet.Tracer = self:GetStatL("TracerCount") or 3
|
|
end
|
|
|
|
self.MainBullet.TracerName = TracerName
|
|
self.MainBullet.PenetrationCount = 0
|
|
self.MainBullet.PenetrationPower = self:GetStatL("Primary.PenetrationPower") * sv_tfa_bullet_penetration_power_mul:GetFloat(1)
|
|
self.MainBullet.InitialPenetrationPower = self.MainBullet.PenetrationPower
|
|
self.MainBullet.AmmoType = self:GetPrimaryAmmoType()
|
|
self.MainBullet.Force = self:GetStatL("Primary.Force") * cv_forcemult:GetFloat() * self:GetAmmoForceMultiplier()
|
|
self.MainBullet.Damage = damage
|
|
self.MainBullet.InitialDamage = damage
|
|
self.MainBullet.InitialForce = self.MainBullet.Force
|
|
self.MainBullet.InitialPosition = Vector(self.MainBullet.Src)
|
|
self.MainBullet.HasAppliedRange = false
|
|
|
|
if self.CustomBulletCallback then
|
|
self.MainBullet.Callback2 = self.CustomBulletCallback
|
|
else
|
|
self.MainBullet.Callback2 = nil
|
|
end
|
|
|
|
if num_bullets > 1 then
|
|
local ang_ = self.MainBullet.Dir:Angle()
|
|
local up, right = ang_:Up(), ang_:Right()
|
|
|
|
-- single callback per multiple bullets fix
|
|
for i = 1, num_bullets do
|
|
local bullet = table.Copy(self.MainBullet)
|
|
|
|
local ang = Angle(ang_)
|
|
|
|
local dYaw, dPitch = self:ComputeBulletDeviation(i, num_bullets, aimcone)
|
|
ang:RotateAroundAxis(up, dYaw)
|
|
ang:RotateAroundAxis(right, dPitch)
|
|
|
|
bullet.Dir = ang:Forward()
|
|
|
|
function bullet.Callback(attacker, trace, dmginfo)
|
|
if not IsValid(self) then return end
|
|
|
|
dmginfo:SetInflictor(self)
|
|
dmginfo:SetDamage(dmginfo:GetDamage() * bullet:CalculateFalloff(trace.HitPos))
|
|
|
|
if bullet.Callback2 then
|
|
bullet.Callback2(attacker, trace, dmginfo)
|
|
end
|
|
|
|
self:CallAttFunc("CustomBulletCallback", attacker, trace, dmginfo)
|
|
|
|
bullet:Penetrate(attacker, trace, dmginfo, self, {})
|
|
self:PCFTracer(bullet, trace.HitPos or vector_origin)
|
|
end
|
|
|
|
BallisticFirebullet(owner, bullet, nil, ang)
|
|
end
|
|
|
|
return
|
|
end
|
|
|
|
function self.MainBullet.Callback(attacker, trace, dmginfo)
|
|
if not IsValid(self) then return end
|
|
|
|
dmginfo:SetInflictor(self)
|
|
dmginfo:SetDamage(dmginfo:GetDamage() * self.MainBullet:CalculateFalloff(trace.HitPos))
|
|
|
|
if self.MainBullet.Callback2 then
|
|
self.MainBullet.Callback2(attacker, trace, dmginfo)
|
|
end
|
|
|
|
self:CallAttFunc("CustomBulletCallback", attacker, trace, dmginfo)
|
|
|
|
self.MainBullet:Penetrate(attacker, trace, dmginfo, self, {})
|
|
self:PCFTracer(self.MainBullet, trace.HitPos or vector_origin)
|
|
end
|
|
|
|
BallisticFirebullet(owner, self.MainBullet)
|
|
end
|
|
|
|
local sp = game.SinglePlayer()
|
|
|
|
function SWEP:TFAMove(ply, movedata)
|
|
local velocity = self:GetQueuedRecoil()
|
|
|
|
if velocity:Length() ~= 0 then
|
|
movedata:SetVelocity(movedata:GetVelocity() + velocity)
|
|
self:SetQueuedRecoil(vector_origin)
|
|
end
|
|
end
|
|
|
|
hook.Add("Move", "TFAMove", function(self, movedata)
|
|
local weapon = self:GetActiveWeapon()
|
|
|
|
if IsValid(weapon) and weapon.IsTFAWeapon then
|
|
weapon:TFAMove(self, movedata)
|
|
end
|
|
end)
|
|
|
|
local sv_tfa_recoil_mul_p = GetConVar("sv_tfa_recoil_mul_p")
|
|
local sv_tfa_recoil_mul_p_npc = GetConVar("sv_tfa_recoil_mul_p_npc")
|
|
local sv_tfa_recoil_mul_y = GetConVar("sv_tfa_recoil_mul_y")
|
|
local sv_tfa_recoil_mul_y_npc = GetConVar("sv_tfa_recoil_mul_y_npc")
|
|
|
|
local sv_tfa_recoil_viewpunch_mul = GetConVar("sv_tfa_recoil_viewpunch_mul")
|
|
local sv_tfa_recoil_eyeangles_mul = GetConVar("sv_tfa_recoil_eyeangles_mul")
|
|
|
|
function SWEP:SetRecoilVector(vector)
|
|
if self:GetOwner():IsPlayer() then
|
|
self:SetQueuedRecoil(vector)
|
|
else
|
|
self:GetOwner():SetVelocity(vector)
|
|
end
|
|
end
|
|
|
|
function SWEP:QueueRecoil(vector)
|
|
if self:GetOwner():IsPlayer() then
|
|
self:SetQueuedRecoil(vector + self:GetQueuedRecoil())
|
|
else
|
|
self:GetOwner():SetVelocity(vector)
|
|
end
|
|
end
|
|
|
|
function SWEP:Recoil(recoil, ifp)
|
|
if sp and type(recoil) == "string" then
|
|
local _, CurrentRecoil = self:CalculateConeRecoil()
|
|
self:Recoil(CurrentRecoil, true)
|
|
|
|
return
|
|
end
|
|
|
|
local owner = self:GetOwner()
|
|
local isplayer = owner:IsPlayer()
|
|
|
|
self:SetLastRecoil(CurTime())
|
|
self:SetSpreadRatio(l_mathClamp(self:GetSpreadRatio() + self:GetStatL("Primary.SpreadIncrement"), 1, self:GetStatL("Primary.SpreadMultiplierMax")))
|
|
self:QueueRecoil(-owner:GetAimVector() * self:GetStatL("Primary.Knockback") * cv_forcemult:GetFloat() * recoil / 5)
|
|
|
|
local seed = self:GetSeed() + 1
|
|
|
|
local kickP = util.SharedRandom("TFA_KickDown", self:GetStatL("Primary.KickDown"), self:GetStatL("Primary.KickUp"), seed) * recoil * -1
|
|
local kickY = util.SharedRandom("TFA_KickHorizontal", -self:GetStatL("Primary.KickHorizontal"), self:GetStatL("Primary.KickHorizontal"), seed) * recoil
|
|
|
|
if isplayer then
|
|
kickP, kickY = kickP * sv_tfa_recoil_mul_p:GetFloat(), kickY * sv_tfa_recoil_mul_y:GetFloat()
|
|
else
|
|
kickP, kickY = kickP * sv_tfa_recoil_mul_p_npc:GetFloat(), kickY * sv_tfa_recoil_mul_y_npc:GetFloat()
|
|
end
|
|
|
|
local factor = 1 - self:GetStatL("Primary.StaticRecoilFactor")
|
|
|
|
if self:GetIronSights() then
|
|
factor = factor * Lerp(self:GetIronSightsProgress(), 1, self:GetStatL("Primary.IronRecoilMultiplier", 0.5))
|
|
end
|
|
|
|
factor = factor * Lerp(self:GetCrouchingRatio(), 1, self:GetStatL("CrouchAccuracyMultiplier", 0.5))
|
|
|
|
local punchY = kickY * factor
|
|
local deltaP = 0
|
|
local deltaY = 0
|
|
|
|
if self:HasRecoilLUT() then
|
|
local ang = self:GetRecoilLUTAngle()
|
|
|
|
if self:GetPrevRecoilAngleTime() < CurTime() then
|
|
self:SetPrevRecoilAngleTime(CurTime() + 0.1)
|
|
self:SetPrevRecoilAngle(ang)
|
|
end
|
|
|
|
local prev_recoil_angle = self:GetPrevRecoilAngle()
|
|
deltaP = ang.p - prev_recoil_angle.p
|
|
deltaY = ang.y - prev_recoil_angle.y
|
|
self:SetPrevRecoilAngle(ang)
|
|
end
|
|
|
|
if isplayer then
|
|
local maxdist = math.min(math.max(0, 89 + owner:EyeAngles().p - math.abs(owner:GetViewPunchAngles().p * 2)), 88.5)
|
|
local punchP = l_mathClamp((kickP + deltaP * self:GetStatL("Primary.RecoilLUT_ViewPunchMult")) * factor, -maxdist, maxdist)
|
|
|
|
owner:ViewPunch(Angle(punchP * sv_tfa_recoil_viewpunch_mul:GetFloat(), (punchY + deltaY * self:GetStatL("Primary.RecoilLUT_ViewPunchMult")) * sv_tfa_recoil_viewpunch_mul:GetFloat()))
|
|
end
|
|
|
|
if (not isplayer or not sv_tfa_recoil_legacy:GetBool()) and not self:HasRecoilLUT() then
|
|
local maxdist2 = l_mathClamp(30 - math.abs(self:GetViewPunchP()), 0, 30)
|
|
local punchP2 = l_mathClamp(kickP, -maxdist2, maxdist2) * factor
|
|
|
|
self:SetViewPunchP(self:GetViewPunchP() + punchP2 * 1.5)
|
|
self:SetViewPunchY(self:GetViewPunchY() + punchY * 1.5)
|
|
self:SetViewPunchBuild(math.min(3, self:GetViewPunchBuild() + math.sqrt(math.pow(punchP2, 2) + math.pow(punchY, 2)) / 3) + 0.2)
|
|
end
|
|
|
|
if isplayer and ((game.SinglePlayer() and SERVER) or (CLIENT and ifp)) then
|
|
local neweyeang = owner:EyeAngles()
|
|
|
|
local ap, ay = (kickP + deltaP * self:GetStatL("Primary.RecoilLUT_AnglePunchMult")) * self:GetStatL("Primary.StaticRecoilFactor") * sv_tfa_recoil_eyeangles_mul:GetFloat(),
|
|
(kickY + deltaY * self:GetStatL("Primary.RecoilLUT_AnglePunchMult")) * self:GetStatL("Primary.StaticRecoilFactor") * sv_tfa_recoil_eyeangles_mul:GetFloat()
|
|
|
|
neweyeang.p = neweyeang.p + ap
|
|
neweyeang.y = neweyeang.y + ay
|
|
--neweyeang.p = l_mathClamp(neweyeang.p, -90 + math.abs(owner:GetViewPunchAngles().p), 90 - math.abs(owner:GetViewPunchAngles().p))
|
|
owner:SetEyeAngles(neweyeang)
|
|
end
|
|
end
|
|
|
|
--[[
|
|
Function Name: GetAmmoRicochetMultiplier
|
|
Syntax: self:GetAmmoRicochetMultiplier().
|
|
Returns: The ricochet multiplier for our ammotype. More is more chance to ricochet.
|
|
Notes: Only compatible with default ammo types, unless you/I mod that. BMG ammotype is detected based on name and category.
|
|
Purpose: Utility
|
|
]]
|
|
--
|
|
function SWEP:GetAmmoRicochetMultiplier()
|
|
local am = string.lower(self:GetStatL("Primary.Ammo"))
|
|
|
|
if (am == "pistol") then
|
|
return 1.25
|
|
elseif (am == "357") then
|
|
return 0.75
|
|
elseif (am == "smg1") then
|
|
return 1.1
|
|
elseif (am == "ar2") then
|
|
return 0.9
|
|
elseif (am == "buckshot") then
|
|
return 2
|
|
elseif (am == "slam") then
|
|
return 1.5
|
|
elseif (am == "airboatgun") then
|
|
return 0.8
|
|
elseif (am == "sniperpenetratedround") then
|
|
return 0.5
|
|
else
|
|
return 1
|
|
end
|
|
end
|
|
|
|
--[[
|
|
Function Name: GetMaterialConcise
|
|
Syntax: self:GetMaterialConcise().
|
|
Returns: The string material name.
|
|
Notes: Always lowercase.
|
|
Purpose: Utility
|
|
]]
|
|
--
|
|
function SWEP:GetAmmoForceMultiplier()
|
|
-- pistol, 357, smg1, ar2, buckshot, slam, SniperPenetratedRound, AirboatGun
|
|
--AR2=Rifle ~= Caliber>.308
|
|
--SMG1=SMG ~= Small/Medium Calber ~= 5.56 or 9mm
|
|
--357=Revolver ~= .357 through .50 magnum
|
|
--Pistol = Small or Pistol Bullets ~= 9mm, sometimes .45ACP but rarely. Generally light.
|
|
--Buckshot = Buckshot = Light, barely-penetrating sniper bullets.
|
|
--Slam = Medium Shotgun Round
|
|
--AirboatGun = Heavy, Penetrating Shotgun Round
|
|
--SniperPenetratedRound = Heavy Large Rifle Caliber ~= .50 Cal blow-yer-head-off
|
|
local am = string.lower(self:GetStatL("Primary.Ammo"))
|
|
|
|
if (am == "pistol") then
|
|
return 0.4
|
|
elseif (am == "357") then
|
|
return 0.6
|
|
elseif (am == "smg1") then
|
|
return 0.475
|
|
elseif (am == "ar2") then
|
|
return 0.6
|
|
elseif (am == "buckshot") then
|
|
return 0.5
|
|
elseif (am == "slam") then
|
|
return 0.5
|
|
elseif (am == "airboatgun") then
|
|
return 0.7
|
|
elseif (am == "sniperpenetratedround") then
|
|
return 1
|
|
else
|
|
return 1
|
|
end
|
|
end
|
|
|
|
--[[
|
|
Function Name: GetPenetrationMultiplier
|
|
Syntax: self:GetPenetrationMultiplier( concise material name).
|
|
Returns: The multilier for how much you can penetrate through a material.
|
|
Notes: Should be used with GetMaterialConcise.
|
|
Purpose: Utility
|
|
]]
|
|
--
|
|
SWEP.Primary.PenetrationMaterials = {
|
|
[MAT_DEFAULT] = 1,
|
|
[MAT_VENT] = 0.4, --Since most is aluminum and stuff
|
|
[MAT_METAL] = 0.6, --Since most is aluminum and stuff
|
|
[MAT_WOOD] = 0.2,
|
|
[MAT_PLASTIC] = 0.23,
|
|
[MAT_FLESH] = 0.48,
|
|
[MAT_CONCRETE] = 0.87,
|
|
[MAT_GLASS] = 0.16,
|
|
[MAT_SAND] = 1,
|
|
[MAT_SLOSH] = 1,
|
|
[MAT_DIRT] = 0.95, --This is plaster, not dirt, in most cases.
|
|
[MAT_FOLIAGE] = 0.9
|
|
}
|
|
|
|
local fac
|
|
|
|
function SWEP:GetPenetrationMultiplier(mat)
|
|
fac = self.Primary.PenetrationMaterials[mat or MAT_DEFAULT] or self.Primary.PenetrationMaterials[MAT_DEFAULT]
|
|
|
|
return fac * (self:GetStatL("Primary.PenetrationMultiplier") and self:GetStatL("Primary.PenetrationMultiplier") or 1)
|
|
end
|
|
|
|
local decalbul = {
|
|
Num = 1,
|
|
Spread = vector_origin,
|
|
Tracer = 0,
|
|
TracerName = "",
|
|
Force = 0,
|
|
Damage = 0,
|
|
Distance = 40
|
|
}
|
|
|
|
local maxpen
|
|
local penetration_max_cvar = GetConVar("sv_tfa_penetration_hardlimit")
|
|
local penetration_cvar = GetConVar("sv_tfa_bullet_penetration")
|
|
local ricochet_cvar = GetConVar("sv_tfa_bullet_ricochet")
|
|
local cv_rangemod = GetConVar("sv_tfa_range_modifier")
|
|
local cv_decalbul = GetConVar("sv_tfa_fx_penetration_decal")
|
|
local atype
|
|
local develop = GetConVar("developer")
|
|
local sv_tfa_debug = GetConVar("sv_tfa_debug")
|
|
|
|
function SWEP:SetBulletTracerName(nm)
|
|
self.BulletTracerName = nm or self.BulletTracerName or ""
|
|
end
|
|
|
|
local debugcolors = {
|
|
Color(166, 91, 236),
|
|
Color(91, 142, 236),
|
|
Color(29, 197, 208),
|
|
Color(61, 232, 109),
|
|
Color(194, 232, 61),
|
|
Color(232, 178, 61),
|
|
Color(232, 61, 129),
|
|
Color(128, 31, 109),
|
|
}
|
|
|
|
local nextdebugcol = -1
|
|
local debugsphere1 = Color(149, 189, 230)
|
|
local debugsphere2 = Color(34, 43, 53)
|
|
local debugsphere3 = Color(255, 255, 255)
|
|
local debugsphere4 = Color(0, 0, 255)
|
|
local debugsphere5 = Color(12, 255, 0)
|
|
|
|
local IsInWorld, IsInWorld2
|
|
|
|
do
|
|
local tr = {collisiongroup = COLLISION_GROUP_WORLD}
|
|
|
|
function IsInWorld2(pos)
|
|
tr.start = pos
|
|
tr.endpos = pos
|
|
return not util.TraceLine(tr).AllSolid
|
|
end
|
|
end
|
|
|
|
if CLIENT then
|
|
IsInWorld = IsInWorld2
|
|
else
|
|
IsInWorld = util.IsInWorld
|
|
end
|
|
|
|
local MAX_CORRECTION_ITERATIONS = 20
|
|
|
|
-- bullettable can be nil
|
|
function SWEP:CalculateFalloff(InitialPosition, HitPos, bullettable)
|
|
local dist = InitialPosition:Distance(HitPos)
|
|
|
|
if not self.Primary.RangeFalloffLUTBuilt then return 1 end
|
|
|
|
local target = self.Primary.RangeFalloffLUTBuilt
|
|
|
|
if dist <= target[1][1] then
|
|
return target[1][2]
|
|
end
|
|
|
|
if dist >= target[#target][1] then
|
|
return target[#target][2]
|
|
end
|
|
|
|
for i = 1, #target - 1 do
|
|
local a, b = target[i], target[i + 1]
|
|
|
|
if a[1] <= dist and b[1] >= dist then
|
|
return Lerp((dist - a[1]) / (b[1] - a[1]), a[2], b[2])
|
|
end
|
|
end
|
|
|
|
return target[#target][2] -- wtf?
|
|
end
|
|
|
|
function SWEP.MainBullet:CalculateFalloff(HitPos)
|
|
return self.Wep:CalculateFalloff(self.InitialPosition, HitPos, self)
|
|
end
|
|
|
|
local function shouldDisplayDebug()
|
|
return SERVER and sv_tfa_debug:GetBool() and develop:GetBool() and DLib
|
|
end
|
|
|
|
function SWEP.MainBullet:Penetrate(ply, traceres, dmginfo, weapon, penetrated, previousStartPos)
|
|
if hook.Run("TFA_Bullet_Penetrate", weapon, ply, traceres, dmginfo, penetrated, previousStartPos) == false then return end
|
|
|
|
--debugoverlay.Sphere( self.Src, 5, 5, color_white, true)
|
|
|
|
DisableOwnerDamage(ply, traceres, dmginfo)
|
|
|
|
if self.TracerName and self.TracerName ~= "" then
|
|
weapon.BulletTracerName = self.TracerName
|
|
|
|
if game.SinglePlayer() then
|
|
weapon:CallOnClient("SetBulletTracerName", weapon.BulletTracerName)
|
|
end
|
|
end
|
|
|
|
if not IsValid(weapon) then return end
|
|
|
|
local hitent = traceres.Entity
|
|
|
|
self:HandleDoor(ply, traceres, dmginfo, weapon)
|
|
|
|
atype = weapon:GetStatL("Primary.DamageType")
|
|
dmginfo:SetDamageType(atype)
|
|
|
|
if SERVER and IsValid(ply) and ply:IsPlayer() and IsValid(hitent) and (hitent:IsPlayer() or hitent:IsNPC() or type(hitent) == "NextBot") then
|
|
weapon:SendHitMarker(ply, traceres, dmginfo)
|
|
end
|
|
|
|
if IsValid(traceres.Entity) and traceres.Entity:GetClass() == "npc_sniper" then
|
|
traceres.Entity.TFAHP = (traceres.Entity.TFAHP or 100) - dmginfo:GetDamage()
|
|
|
|
if traceres.Entity.TFAHP <= 0 then
|
|
traceres.Entity:Fire("SetHealth", "", -1)
|
|
end
|
|
end
|
|
|
|
local cl = hitent:GetClass()
|
|
|
|
if cl == "npc_helicopter" then
|
|
dmginfo:SetDamageType(bit.bor(dmginfo:GetDamageType(), DMG_AIRBOAT))
|
|
end
|
|
|
|
-- custom damage checks
|
|
if atype ~= DMG_BULLET then
|
|
--[[if cl == "npc_strider" and (dmginfo:IsDamageType(DMG_SHOCK) or dmginfo:IsDamageType(DMG_BLAST)) and traceres.Hit and IsValid(hitent) and hitent.Fire then
|
|
hitent:SetHealth(math.max(hitent:Health() - dmginfo:GetDamage(), 2))
|
|
|
|
if hitent:Health() <= 3 then
|
|
hitent:Extinguish()
|
|
hitent:Fire("sethealth", "-1", 0.01)
|
|
dmginfo:ScaleDamage(0)
|
|
end
|
|
end]]
|
|
|
|
if dmginfo:IsDamageType(DMG_BURN) and weapon.Primary.DamageTypeHandled and traceres.Hit and IsValid(hitent) and not traceres.HitWorld and not traceres.HitSky and dmginfo:GetDamage() > 1 and hitent.Ignite then
|
|
hitent:Ignite(dmginfo:GetDamage() / 2, 1)
|
|
end
|
|
|
|
if dmginfo:IsDamageType(DMG_BLAST) and weapon.Primary.DamageTypeHandled and traceres.Hit and not traceres.HitSky then
|
|
local tmpdmg = dmginfo:GetDamage()
|
|
dmginfo:SetDamageForce(dmginfo:GetDamageForce() / 2)
|
|
util.BlastDamageInfo(dmginfo, traceres.HitPos, weapon:GetStatL("Primary.BlastRadius") or (tmpdmg / 2) )
|
|
--util.BlastDamage(weapon, weapon:GetOwner(), traceres.HitPos, tmpdmg / 2, tmpdmg)
|
|
local fx = EffectData()
|
|
fx:SetOrigin(traceres.HitPos)
|
|
fx:SetNormal(traceres.HitNormal)
|
|
|
|
if weapon:GetStatL("Primary.ImpactEffect") then
|
|
TFA.Effects.Create(weapon:GetStatL("Primary.ImpactEffect"), fx)
|
|
elseif tmpdmg > 90 then
|
|
TFA.Effects.Create("HelicopterMegaBomb", fx)
|
|
TFA.Effects.Create("Explosion", fx)
|
|
elseif tmpdmg > 45 then
|
|
TFA.Effects.Create("cball_explode", fx)
|
|
else
|
|
TFA.Effects.Create("MuzzleEffect", fx)
|
|
end
|
|
|
|
dmginfo:ScaleDamage(0.15)
|
|
end
|
|
end
|
|
|
|
if self:Ricochet(ply, traceres, dmginfo, weapon) then
|
|
if shouldDisplayDebug() then
|
|
DLib.debugoverlay.Text(traceres.HitPos - Vector(0, 0, 12), 'ricochet', 10)
|
|
end
|
|
|
|
return
|
|
end
|
|
|
|
if penetration_cvar and not penetration_cvar:GetBool() then return end
|
|
if self.PenetrationCount > math.min(penetration_max_cvar:GetInt(100), weapon:GetStatL("Primary.MaxSurfacePenetrationCount", math.huge)) then return end
|
|
-- source engine quirk - if bullet is fired too close to brush surface
|
|
-- it is assumed to be fired right in front of it, rather than exact
|
|
-- position you specified.
|
|
if previousStartPos and previousStartPos:Distance(traceres.HitPos) < 0.05 then return end
|
|
local oldTraceResHitPos = Vector(traceres.HitPos)
|
|
|
|
local mult = weapon:GetPenetrationMultiplier(traceres.MatType)
|
|
local newdir = (traceres.HitPos - traceres.StartPos):GetNormalized()
|
|
local desired_length = l_mathClamp(self.PenetrationPower / mult, 0, l_mathClamp(sv_tfa_bullet_penetration_power_mul:GetFloat() * 100, 1000, 8000))
|
|
local penetrationoffset = newdir * desired_length
|
|
|
|
local pentrace = {
|
|
start = traceres.HitPos,
|
|
endpos = traceres.HitPos + penetrationoffset,
|
|
mask = MASK_SHOT,
|
|
filter = penetrated
|
|
}
|
|
|
|
local isent = IsValid(traceres.Entity)
|
|
local startpos, decalstartpos
|
|
|
|
if isent then
|
|
table.insert(penetrated, traceres.Entity)
|
|
else
|
|
pentrace.start:Add(traceres.Normal)
|
|
pentrace.start:Add(traceres.Normal)
|
|
pentrace.collisiongroup = COLLISION_GROUP_WORLD
|
|
pentrace.filter = NULL
|
|
end
|
|
|
|
local pentraceres = util.TraceLine(pentrace)
|
|
local pentraceres2, pentrace2
|
|
local loss
|
|
local realstartpos
|
|
|
|
if not isent then
|
|
local acc_length = pentraceres.HitPos:Distance(pentraceres.StartPos)
|
|
|
|
local ostart = pentrace.start
|
|
local FractionLeftSolid = pentraceres.FractionLeftSolid
|
|
local iter = 0
|
|
|
|
local cond = (pentraceres.AllSolid or not IsInWorld2(pentraceres.HitPos)) and acc_length < desired_length
|
|
|
|
while (pentraceres.AllSolid or not IsInWorld2(pentraceres.HitPos)) and acc_length <= desired_length and iter < MAX_CORRECTION_ITERATIONS do
|
|
iter = iter + 1
|
|
|
|
pentrace.start = pentraceres.HitPos + newdir * 8
|
|
|
|
if shouldDisplayDebug() then
|
|
DLib.debugoverlay.Cross(pentrace.start, 8, 10, Color(iter / MAX_CORRECTION_ITERATIONS * 255, iter / MAX_CORRECTION_ITERATIONS * 255, iter / MAX_CORRECTION_ITERATIONS * 255), true)
|
|
end
|
|
|
|
pentraceres = util.TraceLine(pentrace)
|
|
acc_length = acc_length + pentraceres.HitPos:Distance(pentraceres.StartPos) + 8
|
|
end
|
|
|
|
if cond and not (pentraceres.AllSolid or not IsInWorld2(pentraceres.HitPos)) then
|
|
pentraceres.FractionLeftSolid = ostart:Distance(pentrace.start) / ostart:Distance(pentrace.endpos) + pentraceres.FractionLeftSolid + 0.02
|
|
pentrace.start = ostart
|
|
pentraceres.StartPos = ostart
|
|
else
|
|
pentraceres.FractionLeftSolid = FractionLeftSolid
|
|
pentrace.start = ostart
|
|
pentraceres.StartPos = ostart
|
|
end
|
|
end
|
|
|
|
if isent then
|
|
startpos = pentraceres.HitPos - newdir
|
|
local ent = traceres.Entity
|
|
|
|
pentrace2 = {
|
|
start = startpos,
|
|
endpos = pentrace.start,
|
|
mask = MASK_SHOT,
|
|
ignoreworld = true,
|
|
filter = function(ent2)
|
|
return ent2 == ent
|
|
end
|
|
}
|
|
|
|
pentraceres2 = util.TraceLine(pentrace2)
|
|
loss = pentraceres2.HitPos:Distance(pentrace.start) * mult
|
|
|
|
if pentraceres2.HitPos:Distance(pentrace.start) < 0.01 then
|
|
-- bullet stuck in
|
|
loss = self.PenetrationPower
|
|
end
|
|
|
|
decalstartpos = pentraceres2.HitPos + newdir * 3
|
|
|
|
if shouldDisplayDebug() then
|
|
nextdebugcol = (nextdebugcol + 1) % #debugcolors
|
|
DLib.debugoverlay.Line(pentrace.start, pentrace.endpos, 10, debugcolors[nextdebugcol + 1], true)
|
|
DLib.debugoverlay.Cross(pentrace.start, 8, 10, debugsphere1, true)
|
|
DLib.debugoverlay.Cross(pentraceres2.HitPos, 8, 10, debugsphere2, true)
|
|
end
|
|
|
|
if self.IsBallistics then
|
|
startpos = decalstartpos
|
|
end
|
|
|
|
realstartpos = decalstartpos
|
|
else
|
|
startpos = LerpVector(pentraceres.FractionLeftSolid, pentrace.start, pentrace.endpos) + newdir * 4
|
|
realstartpos = startpos
|
|
decalstartpos = startpos + newdir * 2
|
|
loss = startpos:Distance(pentrace.start) * mult
|
|
|
|
if shouldDisplayDebug() then
|
|
nextdebugcol = (nextdebugcol + 1) % #debugcolors
|
|
DLib.debugoverlay.Line(pentrace.start, pentrace.endpos, 10, debugcolors[nextdebugcol + 1], true)
|
|
DLib.debugoverlay.Cross(pentrace.start, 8, 10, debugsphere1, true)
|
|
DLib.debugoverlay.Cross(startpos, 8, 10, debugsphere2, true)
|
|
end
|
|
|
|
if pentraceres.AllSolid then
|
|
return
|
|
elseif not IsInWorld(pentraceres.HitPos) then
|
|
return
|
|
end
|
|
|
|
if not IsInWorld2(startpos) then
|
|
for i = 1, 10 do
|
|
startpos = LerpVector(pentraceres.FractionLeftSolid, pentrace.start, pentrace.endpos) + newdir * ((4 - i) * 3)
|
|
|
|
if IsInWorld2(startpos) then break end
|
|
|
|
startpos = LerpVector(pentraceres.FractionLeftSolid, pentrace.start, pentrace.endpos) + newdir * ((4 + i) * 3)
|
|
|
|
if IsInWorld2(startpos) then break end
|
|
end
|
|
|
|
if not IsInWorld2(startpos) then
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
if self.PenetrationPower - loss <= 0 then
|
|
if shouldDisplayDebug() then
|
|
DLib.debugoverlay.Text(startpos, string.format('Lost penetration power %.3f %.3f', self.PenetrationPower, loss), 10)
|
|
end
|
|
|
|
return
|
|
end
|
|
|
|
self.PenetrationCount = self.PenetrationCount + 1
|
|
local prev = self.PenetrationPower
|
|
self.PenetrationPower = self.PenetrationPower - loss
|
|
|
|
local mfac = self.PenetrationPower / self.InitialPenetrationPower
|
|
|
|
if shouldDisplayDebug() and weapon.Primary.RangeFalloffLUTBuilt then
|
|
DLib.debugoverlay.Text(traceres.HitPos + Vector(0, 0, 12), string.format('NEW Damage falloff final %.3f %.3f %.3f %.3f', self.InitialPosition:Distance(traceres.HitPos), self:CalculateFalloff(traceres.HitPos), mfac * self.InitialDamage * self:CalculateFalloff(traceres.HitPos), mfac), 12)
|
|
end
|
|
|
|
local Damage = self.InitialDamage * self:CalculateFalloff(realstartpos) * mfac
|
|
|
|
local bul = {
|
|
PenetrationPower = self.PenetrationPower,
|
|
PenetrationCount = self.PenetrationCount,
|
|
InitialPenetrationPower = self.InitialPenetrationPower,
|
|
InitialDamage = self.InitialDamage,
|
|
InitialForce = self.InitialForce,
|
|
CalculateFalloff = self.CalculateFalloff,
|
|
InitialPosition = self.InitialPosition,
|
|
Src = startpos,
|
|
Dir = newdir,
|
|
Tracer = 1,
|
|
TracerName = self.TracerName,
|
|
IgnoreEntity = traceres.Entity,
|
|
|
|
Num = 1,
|
|
Force = self.InitialForce * mfac,
|
|
Damage = Damage,
|
|
Penetrate = self.Penetrate,
|
|
MakeDoor = self.MakeDoor,
|
|
HandleDoor = self.HandleDoor,
|
|
Ricochet = self.Ricochet,
|
|
Spread = vector_origin,
|
|
Wep = weapon,
|
|
}
|
|
|
|
if shouldDisplayDebug() then
|
|
DLib.debugoverlay.Text(startpos, string.format('penetrate %.3f->%.3f %d %.3f', prev, self.PenetrationPower, self.PenetrationCount, mfac), 10)
|
|
end
|
|
|
|
function bul.Callback(attacker, trace, dmginfo2)
|
|
if shouldDisplayDebug() then
|
|
DLib.debugoverlay.Cross(trace.HitPos, 8, 10, debugsphere3, true)
|
|
DLib.debugoverlay.Text(trace.HitPos - Vector(0, 0, 7), string.format('hit %.3f %d', mfac, bul.PenetrationCount, bul.PenetrationPower), 10)
|
|
end
|
|
|
|
dmginfo2:SetInflictor(IsValid(bul.Wep) and bul.Wep or IsValid(ply) and ply or Entity(0))
|
|
|
|
bul.Damage = self.InitialDamage * self:CalculateFalloff(trace.HitPos) * mfac
|
|
dmginfo2:SetDamage(bul.Damage)
|
|
|
|
hook.Run("TFA_BulletPenetration", bul, attacker, trace, dmginfo2)
|
|
|
|
-- TODO: User died while bullet make penetration
|
|
-- handle further penetrations even when user is dead
|
|
if IsValid(bul.Wep) then
|
|
bul:Penetrate(attacker, trace, dmginfo2, bul.Wep, penetrated, oldTraceResHitPos)
|
|
end
|
|
end
|
|
|
|
decalbul.Dir = -newdir
|
|
decalbul.Src = decalstartpos
|
|
decalbul.Callback = DisableOwnerDamage
|
|
|
|
if shouldDisplayDebug() then
|
|
DLib.debugoverlay.Cross(decalbul.Src, 8, 10, debugsphere4, true)
|
|
DLib.debugoverlay.Cross(decalbul.Src + decalbul.Dir * decalbul.Distance, 8, 10, debugsphere5, true)
|
|
end
|
|
|
|
if self.PenetrationCount <= 1 and IsValid(weapon) then
|
|
weapon:PCFTracer(self, pentraceres.HitPos or traceres.HitPos, true)
|
|
end
|
|
|
|
local fx = EffectData()
|
|
fx:SetOrigin(bul.Src)
|
|
fx:SetNormal(bul.Dir)
|
|
|
|
fx:SetMagnitude((bul.PenetrationCount + 1) * 1000)
|
|
fx:SetEntity(weapon)
|
|
|
|
if IsValid(pentraceres.Entity) and pentraceres.Entity.EntIndex then
|
|
fx:SetScale(pentraceres.Entity:EntIndex())
|
|
end
|
|
|
|
fx:SetRadius(bul.Damage / 32)
|
|
TFA.Effects.Create("tfa_penetrate", fx)
|
|
|
|
if cv_decalbul:GetBool() then
|
|
ply:FireBullets(decalbul)
|
|
end
|
|
|
|
BallisticFirebullet(ply, bul, true)
|
|
end
|
|
|
|
local RicochetChanceEnum = {
|
|
[MAT_GLASS] = 0,
|
|
[MAT_PLASTIC] = 0.01,
|
|
[MAT_DIRT] = 0.01,
|
|
[MAT_GRASS] = 0.01,
|
|
[MAT_SAND] = 0.01,
|
|
[MAT_CONCRETE] = 0.15,
|
|
[MAT_METAL] = 0.7,
|
|
[MAT_DEFAULT] = 0.5,
|
|
[MAT_FLESH] = 0.0
|
|
}
|
|
|
|
function SWEP.MainBullet:Ricochet(ply, traceres, dmginfo, weapon)
|
|
if ricochet_cvar and not ricochet_cvar:GetBool() then return end
|
|
maxpen = math.min(penetration_max_cvar and penetration_max_cvar:GetInt() - 1 or 1, weapon:GetStatL("Primary.MaxSurfacePenetrationCount", math.huge))
|
|
if self.PenetrationCount > maxpen then return end
|
|
local ricochetchance = RicochetChanceEnum[traceres.MatType] or RicochetChanceEnum[MAT_DEFAULT]
|
|
local dir = traceres.HitPos - traceres.StartPos
|
|
dir:Normalize()
|
|
local dp = dir:Dot(traceres.HitNormal * -1)
|
|
ricochetchance = ricochetchance * weapon:GetAmmoRicochetMultiplier()
|
|
local riccbak = ricochetchance / 0.7
|
|
local ricothreshold = 0.6
|
|
ricochetchance = l_mathClamp(ricochetchance * ( 1 + l_mathClamp(1 - (dp + ricothreshold), 0, 1) ), 0, 1)
|
|
if dp <= ricochetchance and math.Rand(0, 1) < ricochetchance then
|
|
local ric = {}
|
|
ric.Ricochet = self.Ricochet
|
|
ric.Penetrate = self.Penetrate
|
|
ric.MakeDoor = self.MakeDoor
|
|
ric.HandleDoor = self.HandleDoor
|
|
ric.Damage = self.Damage * 0.5
|
|
ric.Force = self.Force * 0.5
|
|
ric.Num = 1
|
|
ric.Spread = vector_origin
|
|
ric.Tracer = 0
|
|
ric.Src = traceres.HitPos
|
|
ric.Dir = ((2 * traceres.HitNormal * dp) + traceres.Normal) + (VectorRand() * 0.02)
|
|
ric.PenetrationCount = self.PenetrationCount + 1
|
|
self.PenetrationCount = self.PenetrationCount + 1
|
|
|
|
if TFA.GetRicochetEnabled() then
|
|
local fx = EffectData()
|
|
fx:SetOrigin(ric.Src)
|
|
fx:SetNormal(ric.Dir)
|
|
fx:SetMagnitude(riccbak)
|
|
TFA.Effects.Create("tfa_ricochet", fx)
|
|
end
|
|
|
|
BallisticFirebullet(ply, ric, true)
|
|
|
|
return true
|
|
end
|
|
end
|
|
|
|
local defaultdoorhealth = 250
|
|
local cv_doorres = GetConVar("sv_tfa_door_respawn")
|
|
|
|
function SWEP.MainBullet:MakeDoor(ent, dmginfo)
|
|
local dir = dmginfo:GetDamageForce():GetNormalized()
|
|
local force = dir * math.max(math.sqrt(dmginfo:GetDamageForce():Length() / 1000), 1) * 1000
|
|
local pos = ent:GetPos()
|
|
local ang = ent:GetAngles()
|
|
local mdl = ent:GetModel()
|
|
local ski = ent:GetSkin()
|
|
ent:SetNotSolid(true)
|
|
ent:SetNoDraw(true)
|
|
local prop = ents.Create("prop_physics")
|
|
prop:SetPos(pos + dir * 16)
|
|
prop:SetAngles(ang)
|
|
prop:SetModel(mdl)
|
|
prop:SetSkin(ski or 0)
|
|
prop:Spawn()
|
|
prop:SetVelocity(force)
|
|
prop:GetPhysicsObject():ApplyForceOffset(force, dmginfo:GetDamagePosition())
|
|
prop:SetPhysicsAttacker(dmginfo:GetAttacker())
|
|
prop:EmitSound("physics/wood/wood_furniture_break" .. tostring(math.random(1, 2)) .. ".wav", 110, math.random(90, 110))
|
|
|
|
if cv_doorres and cv_doorres:GetInt() ~= -1 then
|
|
timer.Simple(cv_doorres:GetFloat(), function()
|
|
if IsValid(prop) then
|
|
prop:Remove()
|
|
end
|
|
|
|
if IsValid(ent) then
|
|
ent.TFADoorHealth = defaultdoorhealth
|
|
ent:SetNotSolid(false)
|
|
ent:SetNoDraw(false)
|
|
end
|
|
end)
|
|
end
|
|
end
|
|
|
|
local cv_doordestruction = GetConVar("sv_tfa_bullet_doordestruction")
|
|
local sv_tfa_bullet_doordestruction_keep = GetConVar("sv_tfa_bullet_doordestruction_keep")
|
|
|
|
function SWEP.MainBullet:HandleDoor(ply, traceres, dmginfo, wep)
|
|
-- Don't do anything if door desstruction isn't enabled
|
|
if not cv_doordestruction:GetBool() then return end
|
|
local ent = traceres.Entity
|
|
if not IsValid(ent) then return end
|
|
if not IsValid(ply) then return end
|
|
if not ents.Create then return end
|
|
if not ply.SetName then return end
|
|
if ent.TFADoorUntouchable and ent.TFADoorUntouchable > CurTime() then return end
|
|
ent.TFADoorHealth = ent.TFADoorHealth or defaultdoorhealth
|
|
if ent:GetClass() ~= "func_door_rotating" and ent:GetClass() ~= "prop_door_rotating" then return end
|
|
local realDamage = dmginfo:GetDamage() * self.Num
|
|
ent.TFADoorHealth = l_mathClamp(ent.TFADoorHealth - realDamage, 0, defaultdoorhealth)
|
|
if ent.TFADoorHealth > 0 then return end
|
|
ply:EmitSound("ambient/materials/door_hit1.wav", 100, math.random(90, 110))
|
|
|
|
if not sv_tfa_bullet_doordestruction_keep:GetBool() and self.Damage * self.Num > 100 then
|
|
self:MakeDoor(ent, dmginfo)
|
|
ent.TFADoorUntouchable = CurTime() + 0.5
|
|
|
|
return
|
|
end
|
|
|
|
ply.oldname = ply:GetName()
|
|
ply:SetName("bashingpl" .. ply:EntIndex())
|
|
ent:Fire("unlock", "", .01)
|
|
ent:SetKeyValue("Speed", "500")
|
|
ent:SetKeyValue("Open Direction", "Both directions")
|
|
ent:SetKeyValue("opendir", "0")
|
|
ent:Fire("openawayfrom", "bashingpl" .. ply:EntIndex(), .01)
|
|
|
|
timer.Simple(0.02, function()
|
|
if IsValid(ply) then
|
|
ply:SetName(ply.oldname)
|
|
end
|
|
end)
|
|
|
|
timer.Simple(0.3, function()
|
|
if IsValid(ent) then
|
|
ent:SetKeyValue("Speed", "100")
|
|
end
|
|
end)
|
|
|
|
timer.Simple(5, function()
|
|
if IsValid(ent) then
|
|
ent.TFADoorHealth = defaultdoorhealth
|
|
end
|
|
end)
|
|
|
|
ent.TFADoorUntouchable = CurTime() + 5
|
|
end |