873 lines
31 KiB
Lua
873 lines
31 KiB
Lua
function SWEP:StillWaiting(cust, reload)
|
|
if self:GetNextPrimaryFire() > CurTime() then return true end
|
|
if self:GetNextSecondaryFire() > CurTime() and (!reload or !(reload and self:GetReloading())) then return true end
|
|
if self:GetAnimLockTime() > CurTime() and (!reload or !(reload and self:GetReloading())) then return true end
|
|
if !cust and self:GetBlindFireFinishTime() > CurTime() then return true end
|
|
if !cust and self:GetCustomize() then return true end
|
|
if self:GetPrimedGrenade() then return true end
|
|
|
|
return false
|
|
end
|
|
|
|
function SWEP:SprintLock(shoot)
|
|
if self:GetSprintLockTime() > CurTime() or self:GetIsSprinting() or self:ShouldLowerWeapon() then
|
|
return true
|
|
end
|
|
|
|
if shoot and self:DoForceSightsBehavior() and (self:GetSprintDelta() > 0 or self:GetSightDelta() < 0.75) and !self:GetBlindFire() then
|
|
return true
|
|
end
|
|
|
|
if self:GetValue("CannotHipFire") and self:GetSightAmount() < 1 and !self:GetBlindFire() then
|
|
return true
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
local function anglerotate(main, off)
|
|
local forward, up, right = main:Forward(), main:Up(), main:Right()
|
|
|
|
main:RotateAroundAxis(right, off.p)
|
|
main:RotateAroundAxis(up, off.y)
|
|
main:RotateAroundAxis(forward, off.r)
|
|
|
|
return main
|
|
end
|
|
|
|
function SWEP:PrimaryAttack()
|
|
if self:GetOwner():IsNPC() then
|
|
self:NPC_PrimaryAttack()
|
|
return
|
|
end
|
|
|
|
if self:GetValue("Melee") and self:GetOwner():KeyDown(IN_USE) and !(self:GetValue("RunawayBurst") and self:GetBurstCount() > 0) then
|
|
-- self.Primary.Automatic = false
|
|
self:SetSafe(false)
|
|
self:Melee()
|
|
return
|
|
end
|
|
|
|
-- if self:GetJammed() then return end
|
|
if self:GetCurrentFiremode() < 0 and self:GetBurstCount() >= -self:GetCurrentFiremode() then return end
|
|
|
|
if self:GetReloading() and self:GetValue("ShotgunReload") then
|
|
if TacRP.ConVars["reload_sg_cancel"]:GetBool() and !self:GetValue("ShotgunFullCancel") then
|
|
self:CancelReload(false)
|
|
self:Idle()
|
|
else
|
|
self:CancelReload(true)
|
|
end
|
|
end
|
|
|
|
if self:SprintLock(true) then return end
|
|
if DarkRP and self:GetNWBool("TacRP_PoliceBiocode") and !self:GetOwner():isCP() then return end
|
|
if self:GetSafe() and !self:GetReloading() then self:ToggleSafety(false) return end
|
|
if self:StillWaiting() then return end
|
|
|
|
if self:GetValue("RequireLockOn") and !(IsValid(self:GetLockOnEntity()) and CurTime() > self:GetLockOnStartTime() + self:GetValue("LockOnTime")) then return end
|
|
|
|
if self:Clip1() < self:GetValue("AmmoPerShot") or self:GetJammed() then
|
|
local ret = self:RunHook("Hook_PreDryfire")
|
|
if ret != true then
|
|
self.Primary.Automatic = false
|
|
if self:GetBlindFire() then
|
|
self:PlayAnimation("blind_dryfire")
|
|
else
|
|
self:PlayAnimation("dryfire")
|
|
end
|
|
self:EmitSound(self:GetValue("Sound_DryFire"), 75, 100, 1, CHAN_ITEM)
|
|
self:SetBurstCount(0)
|
|
self:SetNextPrimaryFire(CurTime() + 0.2)
|
|
self:RunHook("Hook_PostDryfire")
|
|
return
|
|
end
|
|
end
|
|
|
|
if util.SharedRandom("tacRP_shootChance", 0, 1) <= self:GetJamChance(false) then
|
|
local ret = self:RunHook("Hook_PreJam")
|
|
if ret != true then
|
|
if self:GetBurstCount() == 0 then
|
|
self.Primary.Automatic = false
|
|
end
|
|
if self:GetBlindFire() then
|
|
self:PlayAnimation("blind_dryfire")
|
|
else
|
|
self:PlayAnimation("dryfire")
|
|
end
|
|
self:EmitSound(self:GetValue("Sound_Jam"), 75, 100, 1, CHAN_ITEM)
|
|
self:SetBurstCount(0)
|
|
self:SetPatternCount(0)
|
|
self:SetNextPrimaryFire(CurTime() + self:GetValue("JamWaitTime"))
|
|
self:SetNextSecondaryFire(CurTime() + self:GetValue("JamWaitTime"))
|
|
if self:GetValue("JamTakesRound") then
|
|
self:TakePrimaryAmmo(self:GetValue("AmmoPerShot"))
|
|
end
|
|
if self:Clip1() > 0 and !self:GetValue("JamSkipFix") then
|
|
self:SetJammed(true)
|
|
end
|
|
self:RunHook("Hook_PostJam")
|
|
return
|
|
end
|
|
end
|
|
|
|
self:SetBaseSettings()
|
|
|
|
local stop = self:RunHook("Hook_PreShoot")
|
|
if stop then return end
|
|
|
|
local seq = "fire"
|
|
|
|
local idle = true
|
|
|
|
local mult = self:GetValue("ShootTimeMult")
|
|
|
|
if self:GetValue("LastShot") and self:Clip1() == self:GetValue("AmmoPerShot") then
|
|
seq = self:TranslateSequence("lastshot")
|
|
idle = false
|
|
end
|
|
|
|
if self:GetBlindFire() then
|
|
seq = "blind_" .. seq
|
|
end
|
|
|
|
if self:GetValue("Akimbo") and !self:GetBlindFire() then
|
|
if self:GetNthShot() % 2 == 0 then
|
|
seq = "shoot_left"
|
|
else
|
|
seq = "shoot_right"
|
|
end
|
|
|
|
if self:GetValue("LastShot") then
|
|
if self:Clip1() == self:GetValue("AmmoPerShot") then
|
|
seq = seq .. "_lastshot"
|
|
elseif self:Clip1() == self:GetValue("AmmoPerShot") * 2 then
|
|
seq = seq .. "_second_2_lastshot"
|
|
end
|
|
end
|
|
end
|
|
|
|
local prociron = self:DoProceduralIrons()
|
|
if self:GetScopeLevel() > 0 and (prociron or self:HasSequence(seq .. "_iron")) and !self:GetPeeking() then
|
|
if prociron then
|
|
if self:GetValue("LastShot") and self:Clip1() == self:GetValue("AmmoPerShot") then
|
|
self:PlayAnimation(self:TranslateSequence("dryfire"), mult, false)
|
|
end
|
|
self:SetLastProceduralFireTime(CurTime())
|
|
else
|
|
self:PlayAnimation(seq .. "_iron", mult, false, idle)
|
|
end
|
|
elseif self:HasSequence(seq .. "1") then
|
|
local seq1 = seq .. "1"
|
|
if !self:GetInBipod() and (self:GetScopeLevel() < 1 or self:GetPeeking()) then
|
|
seq1 = seq .. tostring(self:GetBurstCount() + 1)
|
|
end
|
|
|
|
if self:HasSequence(seq1) then
|
|
self:PlayAnimation(seq1, mult, false, idle)
|
|
elseif self:GetScopeLevel() < 1 or self:GetPeeking() then
|
|
for i = self:GetBurstCount() + 1, 1, -1 do
|
|
local seq2 = seq .. tostring(i)
|
|
if self:HasSequence(seq2) then
|
|
self:PlayAnimation(seq2, mult, false, idle)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
else
|
|
self:PlayAnimation(seq, mult, false, idle)
|
|
end
|
|
|
|
self:GetOwner():DoCustomAnimEvent(PLAYERANIMEVENT_ATTACK_PRIMARY, 0)
|
|
|
|
local pvar = self:GetValue("ShootPitchVariance")
|
|
|
|
local sshoot = self:GetValue("Sound_Shoot")
|
|
|
|
if self:GetValue("Silencer") then
|
|
sshoot = self:GetValue("Sound_Shoot_Silenced")
|
|
end
|
|
|
|
if istable(sshoot) then
|
|
sshoot = table.Random(sshoot)
|
|
end
|
|
|
|
if self:GetValue("Sound_ShootAdd") then
|
|
self:EmitSound(self:GetValue("Sound_ShootAdd"), self:GetValue("Vol_Shoot"), self:GetValue("Pitch_Shoot") + util.SharedRandom("TacRP_sshoot", -pvar, pvar), self:GetValue("Loudness_Shoot"), CHAN_BODY)
|
|
end
|
|
|
|
-- if we die from suicide, EmitSound will not play, so do this instead
|
|
if self:GetBlindFireMode() == TacRP.BLINDFIRE_KYS then
|
|
if SERVER then
|
|
sound.Play(sshoot, self:GetMuzzleOrigin(), self:GetValue("Vol_Shoot"), self:GetValue("Pitch_Shoot") + util.SharedRandom("TacRP_sshoot", -pvar, pvar), self:GetValue("Loudness_Shoot"))
|
|
end
|
|
else
|
|
self:EmitSound(sshoot, self:GetValue("Vol_Shoot"), self:GetValue("Pitch_Shoot") + util.SharedRandom("TacRP_sshoot", -pvar, pvar), self:GetValue("Loudness_Shoot"), CHAN_WEAPON)
|
|
end
|
|
|
|
local delay = 60 / self:GetRPM()
|
|
-- local delay = 60 / self:GetRPM()
|
|
|
|
local curatt = self:GetNextPrimaryFire()
|
|
local diff = CurTime() - curatt
|
|
|
|
if diff > engine.TickInterval() or diff < 0 then
|
|
curatt = CurTime()
|
|
end
|
|
|
|
self:SetNthShot(self:GetNthShot() + 1)
|
|
|
|
local ejectdelay = self:GetValue("EjectDelay")
|
|
if ejectdelay == 0 then
|
|
self:DoEject()
|
|
else
|
|
self:SetTimer(ejectdelay, function()
|
|
self:DoEject()
|
|
end)
|
|
end
|
|
|
|
self:DoEffects()
|
|
|
|
if self:GetValue("EffectsDoubled") then
|
|
-- self:SetNthShot(self:GetNthShot() + 1)
|
|
self:DoEffects(true)
|
|
if ejectdelay == 0 then
|
|
self:DoEject(true)
|
|
else
|
|
self:SetTimer(ejectdelay, function()
|
|
self:DoEject(true)
|
|
end)
|
|
end
|
|
-- self:SetNthShot(self:GetNthShot() - 1)
|
|
end
|
|
|
|
local num = self:GetValue("Num")
|
|
local fixed_spread = self:IsShotgun() and TacRP.ConVars["fixedspread"]:GetBool()
|
|
local pellet_spread = self:IsShotgun() and self:GetValue("ShotgunPelletSpread") > 0 and TacRP.ConVars["pelletspread"]:GetBool()
|
|
|
|
local spread = self:GetSpread()
|
|
|
|
local dir = self:GetShootDir()
|
|
|
|
local tr = self:GetValue("TracerNum")
|
|
|
|
local shootent = self:GetValue("ShootEnt")
|
|
|
|
if IsFirstTimePredicted() then
|
|
|
|
local hitscan = !TacRP.ConVars["physbullet"]:GetBool()
|
|
|
|
local dist = 100000
|
|
|
|
-- If the bullet is going to hit something very close in front, use hitscan bullets instead
|
|
-- This uses the aim direction without random spread, which may result in hitscan bullets in distances where it shouldn't be.
|
|
if !hitscan and (game.SinglePlayer() or !TacRP.ConVars["client_damage"]:GetBool()) then
|
|
dist = math.max(self:GetValue("MuzzleVelocity"), 15000) * engine.TickInterval()
|
|
* game.GetTimeScale()
|
|
* (num == 1 and 2 or 1) * (game.IsDedicated() and 1 or 2)
|
|
local threshold = dir:Forward() * dist
|
|
local inst_tr = util.TraceLine({
|
|
start = self:GetMuzzleOrigin(),
|
|
endpos = self:GetMuzzleOrigin() + threshold,
|
|
mask = MASK_SHOT,
|
|
filter = {self:GetOwner(), self:GetOwner():GetVehicle(), self},
|
|
})
|
|
if inst_tr.Hit and !inst_tr.HitSky then
|
|
hitscan = true
|
|
end
|
|
-- debugoverlay.Line(self:GetMuzzleOrigin(), self:GetMuzzleOrigin() + threshold, 2, hitscan and Color(255, 0, 255) or Color(255, 255, 255))
|
|
end
|
|
|
|
-- Firebullets already does this so this is just placebo
|
|
-- self:GetOwner():LagCompensation(true)
|
|
|
|
if shootent or !hitscan or fixed_spread then
|
|
local d = math.random() -- self:GetNthShot() / self:GetCapacity()
|
|
for i = 1, num do
|
|
local new_dir = Angle(dir)
|
|
if fixed_spread then
|
|
local sgp_x, sgp_y = self:GetShotgunPattern(i, d)
|
|
// new_dir:Add(Angle(sgp_x, sgp_y, 0) * 36 * 1.4142135623730)
|
|
new_dir = anglerotate(new_dir, Angle(sgp_x, sgp_y, 0) * 36 * 1.4142135623730)
|
|
if pellet_spread then
|
|
// new_dir:Add(self:RandomSpread(self:GetValue("ShotgunPelletSpread"), i))
|
|
new_dir = anglerotate(new_dir, self:RandomSpread(self:GetValue("ShotgunPelletSpread"), i))
|
|
end
|
|
else
|
|
// new_dir:Add(self:RandomSpread(spread, i))
|
|
new_dir = anglerotate(new_dir, self:RandomSpread(spread, i))
|
|
end
|
|
|
|
if shootent then
|
|
self:ShootRocket(new_dir)
|
|
elseif hitscan then
|
|
self:GetOwner():FireBullets({
|
|
Damage = self:GetValue("Damage_Max"),
|
|
Force = 8,
|
|
Tracer = tr,
|
|
TracerName = "tacrp_tracer",
|
|
Num = 1,
|
|
Dir = new_dir:Forward(),
|
|
Src = self:GetMuzzleOrigin(),
|
|
Spread = Vector(),
|
|
IgnoreEntity = self:GetOwner():GetVehicle(),
|
|
Distance = dist,
|
|
HullSize = (self:IsShotgun() and i % 2 == 0) and TacRP.ShotgunHullSize or 0,
|
|
Callback = function(att, btr, dmg)
|
|
local range = (btr.HitPos - btr.StartPos):Length()
|
|
|
|
self:AfterShotFunction(btr, dmg, range, self:GetValue("Penetration"), {})
|
|
-- if SERVER then
|
|
-- debugoverlay.Cross(btr.HitPos, 4, 5, Color(255, 0, 0), false)
|
|
-- else
|
|
-- debugoverlay.Cross(btr.HitPos, 4, 5, Color(255, 255, 255), false)
|
|
-- end
|
|
end
|
|
})
|
|
else
|
|
TacRP:ShootPhysBullet(self, self:GetMuzzleOrigin(), new_dir:Forward() * self:GetValue("MuzzleVelocity"),
|
|
{HullSize = (self:IsShotgun() and i % 2 == 0) and TacRP.ShotgunHullSize or 0,})
|
|
end
|
|
end
|
|
else
|
|
local new_dir = Angle(dir)
|
|
local new_spread = spread
|
|
-- if pellet_spread then
|
|
-- new_spread = self:GetValue("ShotgunPelletSpread")
|
|
-- new_dir:Add(self:RandomSpread(spread, 0))
|
|
-- end
|
|
|
|
-- Try to use Num in FireBullets if at all possible, as this is more performant and better for damage calc compatibility
|
|
-- Also it generates nice big numbers in various hit number addons instead of a buncha small ones.
|
|
self:GetOwner():FireBullets({
|
|
Damage = self:GetValue("Damage_Max"),
|
|
Force = 8,
|
|
Tracer = tr,
|
|
TracerName = "tacrp_tracer",
|
|
Num = num,
|
|
Dir = new_dir:Forward(),
|
|
Src = self:GetMuzzleOrigin(),
|
|
Spread = Vector(new_spread, new_spread, 0),
|
|
IgnoreEntity = self:GetOwner():GetVehicle(),
|
|
Distance = dist,
|
|
Callback = function(att, btr, dmg)
|
|
local range = (btr.HitPos - btr.StartPos):Length()
|
|
|
|
if IsValid(btr.Entity) and (!game.SinglePlayer() and TacRP.ConVars["client_damage"]:GetBool()) then
|
|
if CLIENT then
|
|
net.Start("tacrp_clientdamage")
|
|
net.WriteEntity(self)
|
|
net.WriteEntity(btr.Entity)
|
|
net.WriteVector(btr.Normal)
|
|
net.WriteVector(btr.Entity:WorldToLocal(btr.HitPos))
|
|
net.WriteUInt(btr.HitGroup, 8)
|
|
net.WriteFloat(range)
|
|
net.WriteFloat(self:GetValue("Penetration"))
|
|
net.WriteUInt(0, 4)
|
|
net.SendToServer()
|
|
else
|
|
self:AfterShotFunction(btr, dmg, range, self:GetValue("Penetration"), {[btr.Entity] = true})
|
|
end
|
|
else
|
|
self:AfterShotFunction(btr, dmg, range, self:GetValue("Penetration"), {})
|
|
end
|
|
|
|
if SERVER then
|
|
debugoverlay.Cross(btr.HitPos, 4, 5, Color(255, 0, 0), false)
|
|
else
|
|
debugoverlay.Cross(btr.HitPos, 4, 5, Color(255, 255, 255), false)
|
|
|
|
end
|
|
end
|
|
})
|
|
end
|
|
|
|
-- self:GetOwner():LagCompensation(false)
|
|
end
|
|
|
|
self:ApplyRecoil()
|
|
|
|
self:SetNextPrimaryFire(curatt + delay)
|
|
self:TakePrimaryAmmo(self:GetValue("AmmoPerShot"))
|
|
|
|
self:SetBurstCount(self:GetBurstCount() + 1)
|
|
self:SetPatternCount(self:GetPatternCount() + 1)
|
|
self:DoBulletBodygroups()
|
|
|
|
if self:Clip1() == 0 then self.Primary.Automatic = false end
|
|
|
|
-- FireBullets won't hit ourselves. Apply damage directly!
|
|
if SERVER and self:GetBlindFireMode() == TacRP.BLINDFIRE_KYS and !self:GetValue("ShootEnt") then
|
|
timer.Simple(0, function()
|
|
if !IsValid(self) or !IsValid(self:GetOwner()) then return end
|
|
local damage = DamageInfo()
|
|
damage:SetAttacker(self:GetOwner())
|
|
damage:SetInflictor(self)
|
|
damage:SetDamage(self:GetValue("Damage_Max") * self:GetValue("Num") * self:GetConfigDamageMultiplier())
|
|
damage:SetDamageType(self:GetValue("DamageType") or self:IsShotgun() and DMG_BUCKSHOT or DMG_BULLET)
|
|
damage:SetDamagePosition(self:GetMuzzleOrigin())
|
|
damage:SetDamageForce(dir:Forward() * self:GetValue("Num"))
|
|
|
|
damage:ScaleDamage(self:GetBodyDamageMultipliers()[HITGROUP_HEAD])
|
|
-- self:GetOwner():SetLastHitGroup(HITGROUP_HEAD)
|
|
|
|
self:GetOwner():TakeDamageInfo(damage)
|
|
end)
|
|
end
|
|
|
|
if CLIENT and self:GetOwner() == LocalPlayer() then
|
|
self:DoMuzzleLight()
|
|
elseif game.SinglePlayer() then
|
|
self:CallOnClient("DoMuzzleLight")
|
|
end
|
|
|
|
self:SetCharge(false)
|
|
|
|
-- Troll
|
|
if self:GetBurstCount() >= 8 and TacRP.ShouldWeFunny(true) and (self.NextTroll or 0) < CurTime() and math.random() <= 0.02 then
|
|
timer.Simple(math.Rand(0, 0.25), function()
|
|
if IsValid(self) then
|
|
self:EmitSound("tacrp/discord-notification.wav", nil, 100, math.Rand(0.1, 0.5), CHAN_BODY)
|
|
end
|
|
end)
|
|
self.NextTroll = CurTime() + 180
|
|
end
|
|
|
|
self:RunHook("Hook_PostShoot")
|
|
end
|
|
|
|
local rings = {1, 9, 24, 45}
|
|
local function ringnum(i)
|
|
return rings[i] or (rings[#rings] + i ^ 2)
|
|
end
|
|
|
|
local function getring(x)
|
|
local i = 1
|
|
while x > ringnum(i) do i = i + 1 end
|
|
return i
|
|
end
|
|
|
|
function SWEP:GetShotgunPattern(i, d)
|
|
local ring_spread = self:GetSpread()
|
|
local num = self:GetValue("Num")
|
|
if num == 1 then return 0, 0 end
|
|
|
|
local pelspread = self:GetValue("ShotgunPelletSpread") > 0 and TacRP.ConVars["pelletspread"]:GetBool()
|
|
if pelspread then
|
|
ring_spread = ring_spread - self:GetValue("ShotgunPelletSpread")
|
|
else
|
|
d = 0
|
|
end
|
|
|
|
local x = 0
|
|
local y = 0
|
|
local red = num <= 3 and 0 or 1
|
|
local f = (i - red) / (num - red)
|
|
|
|
if num == 2 then
|
|
local angle = f * 180 + (pelspread and (d - 0.5) * 60 or 0)
|
|
|
|
x = math.sin(math.rad(angle)) * ring_spread
|
|
y = math.cos(math.rad(angle)) * ring_spread
|
|
elseif num == 3 then
|
|
local angle = f * 360 + d * 180 + 30
|
|
x = math.sin(math.rad(angle)) * ring_spread
|
|
y = math.cos(math.rad(angle)) * ring_spread
|
|
elseif i == 1 then
|
|
return x, y
|
|
-- elseif num <= 9 then
|
|
-- local angle = 360 * (f + d - (1 / (num - 2)))
|
|
-- x = math.sin(math.rad(angle)) * ring_spread
|
|
-- y = math.cos(math.rad(angle)) * ring_spread
|
|
else
|
|
local tr = getring(num)
|
|
local ri = getring(i)
|
|
local rin = ringnum(ri)
|
|
local rln = ringnum(ri - 1)
|
|
|
|
local l = (ri - 1) / (tr - 1)
|
|
if ri == tr then
|
|
f = (i - rln) / ((math.min(rin, num)) - rln)
|
|
else
|
|
f = (i - rln) / (rin - rln)
|
|
end
|
|
|
|
local angle = 360 * (f + l + d)
|
|
x = math.sin(math.rad(angle)) * ring_spread * l
|
|
y = math.cos(math.rad(angle)) * ring_spread * l
|
|
end
|
|
|
|
return x, y
|
|
end
|
|
|
|
local doorclasses = {
|
|
["func_door_rotating"] = true,
|
|
["prop_door_rotating"] = true,
|
|
["prop_door_rotating_checkpoint"] = true
|
|
}
|
|
|
|
function SWEP:AfterShotFunction(tr, dmg, range, penleft, alreadypenned, forced)
|
|
if !forced and !IsFirstTimePredicted() and !game.SinglePlayer() then return end
|
|
|
|
if self:GetValue("DamageType") then
|
|
dmg:SetDamageType(self:GetValue("DamageType"))
|
|
elseif self:IsShotgun() then
|
|
dmg:SetDamageType(DMG_BUCKSHOT + (engine.ActiveGamemode() == "terrortown" and DMG_BULLET or 0))
|
|
end
|
|
|
|
local matpen = self:GetValue("Penetration")
|
|
|
|
if tr.Entity and alreadypenned[tr.Entity] then
|
|
dmg:SetDamage(0)
|
|
elseif IsValid(tr.Entity) then
|
|
dmg:SetDamage(self:GetDamageAtRange(range))
|
|
local bodydamage = self:GetBodyDamageMultipliers()
|
|
|
|
if bodydamage[tr.HitGroup] then
|
|
dmg:ScaleDamage(bodydamage[tr.HitGroup])
|
|
end
|
|
|
|
if tr.Entity:IsNextBot() or tr.Entity:IsNPC() then
|
|
dmg:ScaleDamage(self:GetValue("DamageMultNPC"))
|
|
end
|
|
|
|
TacRP.CancelBodyDamage(tr.Entity, dmg, tr.HitGroup)
|
|
|
|
if self:GetOwner():IsNPC() and !TacRP.ConVars["npc_equality"]:GetBool() then
|
|
dmg:ScaleDamage(0.25)
|
|
elseif matpen > 0 and TacRP.ConVars["penetration"]:GetBool() and !self:GetOwner():IsNPC() then
|
|
local pendelta = penleft / matpen
|
|
pendelta = Lerp(pendelta, math.Clamp(matpen * 0.02, 0.25, 0.5), 1)
|
|
dmg:ScaleDamage(pendelta)
|
|
end
|
|
alreadypenned[tr.Entity] = true
|
|
|
|
if tr.Entity.LVS and !self:IsShotgun() then
|
|
dmg:ScaleDamage(0.5)
|
|
dmg:SetDamageForce(dmg:GetDamageForce():GetNormalized() * matpen * 75)
|
|
dmg:SetDamageType(DMG_AIRBOAT + DMG_SNIPER)
|
|
penleft = 0
|
|
end
|
|
|
|
if SERVER and self:GetValue("DamageType") == DMG_BURN and IsValid(tr.Entity) then
|
|
tr.Entity:Ignite(1, 64)
|
|
end
|
|
end
|
|
|
|
if self:GetValue("ExplosiveDamage") > 0 and penleft == matpen then
|
|
-- Add DMG_AIRBOAT to hit helicopters
|
|
-- Need a timer here because only one DamageInfo can exist at a time
|
|
timer.Simple(0.0001, function()
|
|
if !IsValid(self) or !IsValid(self:GetOwner()) then return end
|
|
local dmginfo = DamageInfo()
|
|
dmginfo:SetAttacker(self:GetOwner())
|
|
dmginfo:SetInflictor(self)
|
|
dmginfo:SetDamageType(self:GetValue("ExplosiveDamageType") or (DMG_BLAST + DMG_AIRBOAT))
|
|
dmginfo:SetDamage(self:GetValue("ExplosiveDamage"))
|
|
util.BlastDamageInfo(dmginfo, tr.HitPos, self:GetValue("ExplosiveRadius"))
|
|
end)
|
|
-- penleft = 0
|
|
--util.BlastDamage(self, self:GetOwner(), tr.HitPos, self:GetValue("ExplosiveRadius"), self:GetValue("ExplosiveDamage"))
|
|
end
|
|
|
|
if self:GetValue("ExplosiveEffect") then
|
|
local fx = EffectData()
|
|
fx:SetOrigin(tr.HitPos)
|
|
fx:SetNormal(tr.HitNormal)
|
|
|
|
if bit.band(util.PointContents(tr.HitPos), CONTENTS_WATER) == CONTENTS_WATER then
|
|
util.Effect("WaterSurfaceExplosion", fx, true)
|
|
else
|
|
util.Effect(self:GetValue("ExplosiveEffect"), fx, true)
|
|
end
|
|
end
|
|
|
|
if SERVER and IsValid(tr.Entity) and !tr.Entity.TacRP_DoorBusted
|
|
and doorclasses[tr.Entity:GetClass()] and self:GetValue("DoorBreach") then
|
|
if !tr.Entity.TacRP_BreachThreshold or CurTime() - tr.Entity.TacRP_BreachThreshold[1] > 0.1 then
|
|
tr.Entity.TacRP_BreachThreshold = {CurTime(), 0}
|
|
end
|
|
|
|
tr.Entity.TacRP_BreachThreshold[2] = tr.Entity.TacRP_BreachThreshold[2] + dmg:GetDamage()
|
|
if tr.Entity.TacRP_BreachThreshold[2] > (self:GetValue("DoorBreachThreshold") or 100) then
|
|
tr.Entity:EmitSound("ambient/materials/door_hit1.wav", 80, math.Rand(95, 105))
|
|
for _, otherDoor in pairs(ents.FindInSphere(tr.Entity:GetPos(), 72)) do
|
|
if tr.Entity != otherDoor and otherDoor:GetClass() == tr.Entity:GetClass() then
|
|
local v = (otherDoor.TacRP_BreachThreshold and CurTime() - otherDoor.TacRP_BreachThreshold[1] <= 0.1) and 800 or 200
|
|
TacRP.DoorBust(otherDoor, tr.Normal * v, dmg:GetAttacker())
|
|
break
|
|
end
|
|
end
|
|
TacRP.DoorBust(tr.Entity, tr.Normal * 800, dmg:GetAttacker())
|
|
tr.Entity.TacRP_BreachThreshold = nil
|
|
end
|
|
end
|
|
|
|
self:Penetrate(tr, range, penleft, alreadypenned)
|
|
end
|
|
|
|
function SWEP:GetMinMaxRange(base, static)
|
|
local valfunc = base and self.GetBaseValue or self.GetValue
|
|
|
|
local max, min = valfunc(self, "Damage_Max"), valfunc(self, "Damage_Min")
|
|
return valfunc(self, "Range_Min", static, max < min), valfunc(self, "Range_Max", static, max < min)
|
|
end
|
|
|
|
function SWEP:GetDamageAtRange(range, noround)
|
|
local d = 1
|
|
|
|
local r_min, r_max = self:GetMinMaxRange()
|
|
|
|
if range <= r_min then
|
|
d = 0
|
|
elseif range >= r_max then
|
|
d = 1
|
|
else
|
|
d = (range - r_min) / (r_max - r_min)
|
|
end
|
|
|
|
local dmgv = Lerp(d, self:GetValue("Damage_Max"), self:GetValue("Damage_Min")) * self:GetConfigDamageMultiplier()
|
|
|
|
if !noround then
|
|
dmgv = math.ceil(dmgv)
|
|
end
|
|
|
|
return dmgv
|
|
end
|
|
|
|
function SWEP:GetShootDir(nosway)
|
|
if !IsValid(self:GetOwner()) then return self:GetAngles() end
|
|
local dir = self:GetOwner():EyeAngles()
|
|
|
|
local bf = self:GetBlindFireMode()
|
|
if bf == TacRP.BLINDFIRE_KYS then
|
|
dir.y = dir.y + 180
|
|
elseif bf == TacRP.BLINDFIRE_LEFT then
|
|
dir.y = dir.y + 75
|
|
elseif bf == TacRP.BLINDFIRE_RIGHT then
|
|
dir.y = dir.y - 75
|
|
end
|
|
|
|
local u, r = dir:Up(), dir:Right()
|
|
|
|
local oa = self:GetFreeAimOffset()
|
|
if !nosway then
|
|
oa = oa + self:GetSwayAngles()
|
|
end
|
|
|
|
dir:RotateAroundAxis(u, oa.y)
|
|
-- dir:RotateAroundAxis(r, oa.r)
|
|
dir:RotateAroundAxis(r, -oa.p)
|
|
|
|
dir = dir + self:GetValue("ShootOffsetAngle")
|
|
|
|
return dir
|
|
end
|
|
|
|
function SWEP:ShootRocket(dir)
|
|
if CLIENT then return end
|
|
|
|
local src = self:GetMuzzleOrigin()
|
|
dir = dir or self:GetShootDir()
|
|
|
|
local ent = self:GetValue("ShootEnt")
|
|
|
|
local rocket = ents.Create(ent)
|
|
if !IsValid(rocket) then return end
|
|
|
|
rocket:SetPos(src)
|
|
if self:GetBlindFireMode() != TacRP.BLINDFIRE_KYS then
|
|
rocket:SetOwner(self:GetOwner())
|
|
else
|
|
rocket.Attacker = self:GetOwner()
|
|
end
|
|
rocket.Inflictor = self
|
|
rocket:SetAngles(dir)
|
|
if isfunction(rocket.SetWeapon) then
|
|
rocket:SetWeapon(self)
|
|
end
|
|
if self:GetOwner():IsNPC() then
|
|
rocket.LockOnEntity = self:GetOwner():GetTarget()
|
|
else
|
|
if IsValid(self:GetLockOnEntity()) and CurTime() >= self:GetValue("LockOnTime") + self:GetLockOnStartTime() then
|
|
rocket.LockOnEntity = self:GetLockOnEntity()
|
|
end
|
|
end
|
|
self:RunHook("Hook_PreShootEnt", rocket)
|
|
rocket:Spawn()
|
|
self:RunHook("Hook_PostShootEnt", rocket)
|
|
|
|
local phys = rocket:GetPhysicsObject()
|
|
|
|
if phys:IsValid() and self:GetValue("ShootEntForce") > 0 then
|
|
phys:SetVelocityInstantaneous(dir:Forward() * self:GetValue("ShootEntForce"))
|
|
end
|
|
end
|
|
|
|
function SWEP:GetSpread(baseline)
|
|
local ply = self:GetOwner()
|
|
local spread = self:GetValue("Spread")
|
|
|
|
if baseline then return spread end
|
|
|
|
local hippenalty = self:GetValue("HipFireSpreadPenalty")
|
|
local movepenalty = self:GetValue("MoveSpreadPenalty")
|
|
if TacRP.ConVars["oldschool"]:GetBool() or TacRP.GetBalanceMode() == TacRP.BALANCE_OLDSCHOOL then
|
|
movepenalty = movepenalty + hippenalty * 0.25
|
|
hippenalty = hippenalty * Lerp(12 / (self:GetValue("ScopeFOV") - 1.1), 0.05, 0.5)
|
|
end
|
|
|
|
if self:GetInBipod() and self:GetScopeLevel() == 0 then
|
|
spread = spread + Lerp(1 - self:GetValue("PeekPenaltyFraction"), hippenalty, 0)
|
|
else
|
|
spread = spread + Lerp(self:GetSightAmount() - (self:GetPeeking() and self:GetValue("PeekPenaltyFraction") or 0), hippenalty, 0)
|
|
end
|
|
|
|
if !self:UseAltRecoil() then
|
|
spread = spread + (self:GetRecoilAmount() * self:GetValue("RecoilSpreadPenalty"))
|
|
end
|
|
|
|
local v = ply:GetAbsVelocity()
|
|
local spd = math.min(math.sqrt(v.x * v.x + v.y * v.y) / 250, 1)
|
|
|
|
spread = spread + (spd * movepenalty)
|
|
|
|
local groundtime = CurTime() - (ply.TacRP_LastOnGroundTime or 0)
|
|
local gd = math.Clamp(!ply:IsOnGround() and 0 or groundtime / math.Clamp((ply.TacRP_LastAirDuration or 0) - 0.25, 0.1, 1.5), 0, 1) ^ 0.75
|
|
|
|
if gd < 1 and ply:GetMoveType() != MOVETYPE_NOCLIP then
|
|
local v = (ply:WaterLevel() > 0 or ply:GetMoveType() == MOVETYPE_LADDER) and 0.5 or 0
|
|
spread = spread + Lerp(gd + v, self:GetValue("MidAirSpreadPenalty"), 0)
|
|
end
|
|
|
|
if ply:OnGround() and ply:Crouching() then
|
|
spread = spread + self:GetValue("CrouchSpreadPenalty")
|
|
end
|
|
|
|
if self:GetBlindFire() then
|
|
spread = spread + self:GetValue("BlindFireSpreadPenalty")
|
|
end
|
|
|
|
local quickscopetime = CurTime() - self:GetLastScopeTime()
|
|
|
|
local qsd = (quickscopetime / self:GetValue("QuickScopeTime")) ^ 4
|
|
|
|
if qsd < 1 then
|
|
spread = spread + Lerp(qsd, self:GetValue("QuickScopeSpreadPenalty"), 0)
|
|
end
|
|
|
|
spread = math.max(spread, 0)
|
|
|
|
return spread
|
|
end
|
|
|
|
local type_to_cvar = {
|
|
["2Magnum Pistol"] = "mult_damage_magnum",
|
|
["7Sniper Rifle"] = "mult_damage_sniper",
|
|
-- ["5Shotgun"] = "mult_damage_shotgun",
|
|
|
|
["6Launcher"] = "",
|
|
["7Special Weapon"] = "",
|
|
["8Melee Weapon"] = "",
|
|
["9Equipment"] = "",
|
|
["9Throwable"] = "",
|
|
}
|
|
function SWEP:GetConfigDamageMultiplier()
|
|
if self:IsShotgun() then
|
|
return TacRP.ConVars["mult_damage_shotgun"]:GetFloat()
|
|
elseif self:GetValue("PrimaryMelee") then
|
|
return TacRP.ConVars["mult_damage_melee"]:GetFloat()
|
|
else
|
|
local cvar = type_to_cvar[self.SubCatType] or "mult_damage"
|
|
return TacRP.ConVars[cvar] and TacRP.ConVars[cvar]:GetFloat() or 1
|
|
end
|
|
end
|
|
|
|
local shotgundmgmult = {
|
|
[HITGROUP_HEAD] = 1,
|
|
[HITGROUP_CHEST] = 1,
|
|
[HITGROUP_STOMACH] = 1,
|
|
[HITGROUP_LEFTARM] = 1,
|
|
[HITGROUP_RIGHTARM] = 1,
|
|
[HITGROUP_LEFTLEG] = 1,
|
|
[HITGROUP_RIGHTLEG] = 1,
|
|
[HITGROUP_GEAR] = 1,
|
|
}
|
|
|
|
function SWEP:GetBodyDamageMultipliers(base)
|
|
if self:IsShotgun(base) then -- Shotguns using hull traces will never hit bodygroups
|
|
return table.Copy(shotgundmgmult)
|
|
end
|
|
|
|
local valfunc = base and self.GetBaseValue or self.GetValue
|
|
|
|
local btbl = table.Copy(valfunc(self, "BodyDamageMultipliers"))
|
|
|
|
for k, v in pairs(valfunc(self, "BodyDamageMultipliersExtra") or {}) do
|
|
if v < 0 then
|
|
btbl[k] = math.abs(v)
|
|
else
|
|
btbl[k] = btbl[k] * v
|
|
end
|
|
end
|
|
|
|
local mult = TacRP.ConVars["mult_headshot"]:GetFloat()
|
|
if mult <= 0 then
|
|
btbl[HITGROUP_HEAD] = 1
|
|
elseif mult <= 1 then
|
|
btbl[HITGROUP_HEAD] = Lerp(mult, 1, btbl[HITGROUP_HEAD])
|
|
else
|
|
btbl[HITGROUP_HEAD] = btbl[HITGROUP_HEAD] * mult
|
|
end
|
|
|
|
return btbl
|
|
end
|
|
|
|
function SWEP:FireAnimationEvent( pos, ang, event, options )
|
|
if event != 5004 then return true end
|
|
end
|
|
|
|
-- DO NOT USE AngleRand() to do bullet spread as it generates a random angle in 3 directions when we only use two (roll is not relevant!)
|
|
-- Also, multiplying by 36 is not correct! you need to also multiply by square root of 2. Trig stuff, i forgot why exactly.
|
|
-- Arctic I fixed this THREE FUCKING TIMES on your THREE FUCKING WEAPON BASES do not make me come to Australia and beat the shit out of you
|
|
function SWEP:RandomSpread(spread, seed)
|
|
seed = (seed or 0) + self:EntIndex() + engine.TickCount()
|
|
local a = util.SharedRandom("tacrp_randomspread", 0, 360, seed)
|
|
local angleRand = Angle(math.sin(a), math.cos(a), 0)
|
|
angleRand:Mul(spread * util.SharedRandom("tacrp_randomspread2", 0, 45, seed) * 1.4142135623730)
|
|
|
|
return angleRand
|
|
end
|
|
|
|
function SWEP:IsShotgun(base)
|
|
if base then
|
|
return self:GetBaseValue("Num") > 1 and !self:GetBaseValue("NotShotgun")
|
|
else
|
|
return self:GetValue("Num") > 1 and !self:GetValue("NotShotgun")
|
|
end
|
|
end
|
|
|
|
function SWEP:GetJamChance(base)
|
|
|
|
local valfunc = base and self.GetBaseValue or self.GetValue
|
|
local factor = valfunc(self, "JamFactor")
|
|
if factor <= 0 or !TacRP.ConVars["can_jam"]:GetBool() then return 0 end
|
|
|
|
local default = TacRP.AmmoJamMSB[self:GetAmmoType(base)] or 15
|
|
local msb = (valfunc(self, "JamBaseMSB") or default) / math.sqrt(factor)
|
|
|
|
return 1 / msb
|
|
end
|
|
|
|
function SWEP:GetRPM(base, fm)
|
|
fm = fm or self:GetCurrentFiremode()
|
|
local valfunc = base and self.GetBaseValue or self.GetValue
|
|
local rpm = valfunc(self, "RPM")
|
|
if fm == 1 then
|
|
rpm = rpm * valfunc(self, "RPMMultSemi")
|
|
elseif fm < 0 then
|
|
rpm = rpm * valfunc(self, "RPMMultBurst")
|
|
end
|
|
return rpm
|
|
end
|