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