function SWEP:GetReloadTime(base) local vm = self:GetVM() local valfunc = base and self.GetBaseValue or self.GetValue if !valfunc(self, "ShotgunReload") then local seq = vm:LookupSequence(self:TranslateSequence("reload")) local basetime = vm:SequenceDuration(seq) local mult = valfunc(self, "ReloadTimeMult") / TacRP.ConVars["mult_reloadspeed"]:GetFloat() return basetime * mult else local seq1 = vm:LookupSequence(self:TranslateSequence("reload_start")) -- local seq2 = vm:LookupSequence(self:TranslateSequence("reload")) local seq3 = vm:LookupSequence(self:TranslateSequence("reload_finish")) local time_1 = vm:SequenceDuration(seq1) local time_2 = valfunc(self, "ShotgunUpInTime") --vm:SequenceDuration(seq2) local time_3 = vm:SequenceDuration(seq3) local mult = valfunc(self, "ReloadTimeMult") / TacRP.ConVars["mult_reloadspeed"]:GetFloat() local basetime = time_1 + (time_2 * valfunc(self, "ClipSize")) + time_3 if valfunc(self, "ShotgunThreeload") then basetime = time_1 + (time_2 * valfunc(self, "ClipSize") / 3) + time_3 end return basetime * mult end end function SWEP:GetDeployTime(base) local vm = self:GetVM() local valfunc = base and self.GetBaseValue or self.GetValue local anim = "deploy" local mult = valfunc(self, "DeployTimeMult") if valfunc(self, "TryUnholster") then anim = "unholster" mult = mult * valfunc(self, "UnholsterTimeMult") end local seq = vm:LookupSequence(self:TranslateSequence(anim)) local basetime = vm:SequenceDuration(seq) return basetime * mult end function SWEP:CalcHolsterTime(base) local vm = self:GetVM() local valfunc = base and self.GetBaseValue or self.GetValue local anim = "holster" if valfunc(self, "NoHolsterAnimation") then anim = "deploy" end local mult = valfunc(self, "HolsterTimeMult") local seq = vm:LookupSequence(self:TranslateSequence(anim)) local basetime = vm:SequenceDuration(seq) return basetime * mult end function SWEP:GetMuzzleVelocity(base) local valfunc = base and self.GetBaseValue or self.GetValue local basetime = valfunc(self, "MuzzleVelocity") if valfunc(self, "ShootEnt") then basetime = valfunc(self, "ShootEntForce") end return math.ceil(0.3048 * basetime / 12) end -- function SWEP:GetMeanShotsToFail(base) -- local valfunc = base and self.GetBaseValue or self.GetValue -- local shootchance = valfunc(self, "ShootChance") -- return 1 / (1 - shootchance) -- end function SWEP:GetBestFiremode(base) local valfunc = base and self.GetBaseValue or self.GetValue if valfunc(self, "Firemodes") then local bfm, bfm_i for k, v in pairs(valfunc(self, "Firemodes")) do if !bfm or v == 2 or (bfm <= 1 and v < bfm) then bfm = v bfm_i = k end end return bfm, bfm_i else return valfunc(self, "Firemode") or 0, 1 end end local hitgroups = { [HITGROUP_HEAD] = 0.1, [HITGROUP_CHEST] = 0.2, [HITGROUP_STOMACH] = 0.3, [HITGROUP_LEFTARM] = 0.2, [HITGROUP_LEFTLEG] = 0.2, } local mssd_scoring = { [HITGROUP_HEAD] = {0.15, 0.5, {1, 0.6, 0.3, 0.15, 0.05}}, [HITGROUP_CHEST] = {0.25, 0.75, {1, 0.75, 0.4, 0.2, 0.1}}, [HITGROUP_STOMACH] = {0.25, 0.8, {1, 0.8, 0.5, 0.25, 0.15, 0.05}}, [HITGROUP_LEFTARM] = {0.2, 0.5, {1, 0.85, 0.6, 0.3, 0.2, 0.1, 0.05}}, [HITGROUP_LEFTLEG] = {0.15, 0.5, {1, 0.9, 0.7, 0.4, 0.25, 0.15, 0.1}}, } local mssd_scoring_ttt = { [HITGROUP_HEAD] = {0.25, 0.5, {1, 0.75, 0.50, 0.25, 0.15, 0.10, 0.05, 0.025}}, [HITGROUP_CHEST] = {0.25, 0.75, {1, 0.90, 0.75, 0.55, 0.45, 0.35, 0.25, 0.15, 0.10, 0.05}}, [HITGROUP_STOMACH] = {0.25, 1, {1, 1.00, 0.90, 0.80, 0.60, 0.40, 0.30, 0.20, 0.15, 0.10, 0.05}}, [HITGROUP_LEFTARM] = {0.15, 1, {1, 1.00, 0.80, 0.70, 0.50, 0.30, 0.25, 0.15, 0.10, 0.05, 0.025}}, [HITGROUP_LEFTLEG] = {0.10, 1, {1, 1.00, 0.90, 0.75, 0.60, 0.50, 0.40, 0.30, 0.25, 0.20, 0.15, 0.10, 0.05}}, } SWEP.StatGroupGrades = { {88, "S", Color(230, 60, 60)}, {75, "A", Color(230, 180, 60)}, {60, "B", Color(230, 230, 60)}, {40, "C", Color(60, 230, 60)}, {20, "D", Color(60, 60, 230)}, {00, "E", Color(145, 94, 146)}, { -math.huge, "F", Color(150, 150, 150)}, } SWEP.StatGroups = { { Name = "rating.lethality", Description = "rating.lethality.desc", RatingFunction = function(self, base) -- local score = 0 local valfunc = base and self.GetBaseValue or self.GetValue local bfm = self:GetBestFiremode(base) local rrpm = self:GetRPM(base, bfm) local pbd = valfunc(self, "PostBurstDelay") local ttt = TacRP.GetBalanceMode() == TacRP.BALANCE_TTT local pve = TacRP.GetBalanceMode() == TacRP.BALANCE_PVE local health = pve and 50 or 100 local num = valfunc(self, "Num") local bdm = self:GetBodyDamageMultipliers(base) local bdm_add = 0 for k, v in pairs(hitgroups) do bdm_add = bdm_add + bdm[k] * v end local d_max, d_min = valfunc(self, "Damage_Max"), valfunc(self, "Damage_Min") local explosive_damage = valfunc(self, "ExplosiveDamage") d_max = d_max + explosive_damage d_min = d_min + explosive_damage local dmg_max = math.max(d_max, d_min) local dmg_avg = dmg_max * bdm_add --Lerp(0.2, math.max(d_max, d_min), math.min(d_max, d_min)) * bdm_add -- max single shot damage local mssd = 0 for k, v in pairs(ttt and mssd_scoring_ttt or mssd_scoring) do local stk = math.ceil(health / (dmg_max * (bdm[k] or 1) * (1 + (num - 1) * v[2]))) mssd = mssd + (v[3][stk] or 0) * v[1] -- print(bdm[k], stk, (mssd_scoring[k][stk] or 0)) end if pve then mssd = mssd ^ 1 elseif ttt then mssd = mssd ^ 0.75 end -- avg time to kill local stk = math.ceil(health / (dmg_avg * num)) local ttk_s if stk == 1 then ttk_s = math.Clamp(rrpm / 120, 0, 1) ^ 0.75 else local ttk = (stk - 1) * (60 / rrpm) if bfm < 0 then ttk = ttk + math.floor(ttk / -bfm) * pbd end if pve then ttk_s = math.Clamp(1 - ttk / 2, 0, 1) ^ 2 elseif ttt then ttk_s = math.Clamp(1 - ttk / 3, 0, 1) ^ 3 else ttk_s = math.Clamp(1 - ttk / 1.5, 0, 1) ^ 1.5 end end local scores = {mssd, ttk_s} table.sort(scores) return scores[2] * 75 + scores[1] * 25 end, }, { Name = "rating.suppression", Description = "rating.suppression.desc", RatingFunction = function(self, base) local valfunc = base and self.GetBaseValue or self.GetValue local bfm = self:GetBestFiremode(base) local rrpm = self:GetRPM(base, bfm) local erpm = rrpm local pbd = valfunc(self, "PostBurstDelay") local ttt = TacRP.GetBalanceMode() == TacRP.BALANCE_TTT local pve = TacRP.GetBalanceMode() == TacRP.BALANCE_PVE if bfm == 1 then erpm = math.min(rrpm, 600) + math.max(rrpm - 600, 0) ^ 0.75 -- you can't click *that* fast elseif bfm < 0 then erpm = 60 / ((1 / (rrpm / 60)) + (pbd / -bfm)) end local num = valfunc(self, "Num") local bdm = self:GetBodyDamageMultipliers(base) local bdm_add = 0 for k, v in pairs(hitgroups) do bdm_add = bdm_add + bdm[k] * v end local d_max, d_min = valfunc(self, "Damage_Max"), valfunc(self, "Damage_Min") local explosive_damage = valfunc(self, "ExplosiveDamage") local explosive_radius = valfunc(self, "ExplosiveRadius") explosive_damage = explosive_damage * explosive_radius / 64 * 2 d_max = d_max + explosive_damage d_min = d_min + explosive_damage -- local dmg_max = math.max(d_max, d_min) local dmg_avg = Lerp(0.2, math.max(d_max, d_min), math.min(d_max, d_min)) * bdm_add -- raw dps local dps = dmg_avg * num * erpm / 60 -- average dps over time local dot = dmg_avg * num / (60 / erpm + self:GetReloadTime(base) / (valfunc(self, "ClipSize") / valfunc(self, "AmmoPerShot"))) local dps_s, dot_s if pve then dps_s = math.Clamp((dps - 12.5) / 150, 0, 1) dot_s = math.Clamp((dot - 5) / 100, 0, 1) ^ 0.9 elseif ttt then dps_s = math.Clamp((dps - 25) / 200, 0, 1) dot_s = math.Clamp((dot - 10) / 100, 0, 1) else dps_s = math.Clamp((dps - 50) / 400, 0, 1) ^ 0.9 dot_s = math.Clamp((dot - 20) / 200, 0, 1) ^ 0.9 end local scores = {dps_s, dot_s} table.sort(scores) return scores[2] * 70 + scores[1] * 30 end, }, { Name = "rating.range", Description = "rating.range.desc", RatingFunction = function(self, base) local score = 0 local valfunc = base and self.GetBaseValue or self.GetValue local d_max, d_min = valfunc(self, "Damage_Max"), valfunc(self, "Damage_Min") local r_min, r_max = self:GetMinMaxRange(base) local ttt = TacRP.GetBalanceMode() == TacRP.BALANCE_TTT local r_mid = r_min + (r_max - r_min) / 2 local d_diff = math.abs(d_max - d_min) / math.max(d_max, d_min) if d_max > d_min then -- [100] 50% damage falloff range score = score + math.Clamp((r_mid - 500) / (ttt and 2500 or 3000), 0, 1) ^ 0.75 * 100 -- [0] damage reduction from range -- score = score + math.Clamp(1 - d_diff, 0, 1) ^ 1.5 * 0 else -- [40] free points -- [40] 50% damage rampup range score = score + 40 + math.Clamp(r_mid / (ttt and 1500 or 3000), 0, 1) * 40 -- print(r_mid, math.Clamp(1 - r_mid / 5000, 0, 1)) -- [20] damage reduction from range score = score + math.Clamp(1 - d_diff, 0, 1) * 20 end return score end, }, { Name = "rating.precision", Description = "rating.precision.desc", RatingFunction = function(self, base) local score = 0 local valfunc = base and self.GetBaseValue or self.GetValue local bfm = self:GetBestFiremode(base) local rpm = valfunc(self, "RPM") local num = valfunc(self, "Num") local spread = valfunc(self, "Spread") local rps = valfunc(self, "RecoilPerShot") local rsp = valfunc(self, "RecoilSpreadPenalty") local rrt = self:GetRecoilResetTime(base) local rdr = valfunc(self, "RecoilDissipationRate") local dt = math.max(0, -rrt) local rbs = dt * rdr -- amount of recoil we can recover between shots even if fired ASAP if self:UseAltRecoil() then local min = 0.0001 local tgt = 0.015 if num > 2 then tgt = 0.04 end score = math.Clamp(1 - (spread - min) / tgt, 0, 1) * 100 else -- [50] base spread local min = 0.001 local tgt = 0.015 if num > 2 then tgt = 0.04 end score = score + math.Clamp(1 - (spread - min) / tgt, 0, 1) * 50 local fss = valfunc(self, "RecoilFirstShotMult") * rps -- score = score + math.Clamp(1 - (spread + fss * rsp - rbs) / tgt, 0, 1) * 25 -- [50] spread over 0.3s (or one burst) local shots = math.min(math.ceil(rpm / 60 * 0.3), math.floor(self:GetBaseValue("ClipSize") * 0.5)) if bfm < 0 then shots = -bfm end if rbs <= fss then local so1 = (fss - rbs + shots * (rps - rbs)) * rsp score = score + math.Clamp(1 - so1 / 0.03, 0, 1) ^ 1.25 * 50 else -- delay is so long we always get first shot score = score + 50 end end -- recoil reset time -- score = score + math.Clamp(1 - math.max(0, rrt - delay) / 0.25, 0, 1) * 10 return score end, }, { Name = "rating.control", Description = "rating.control.desc", RatingFunction = function(self, base) local score = 0 local valfunc = base and self.GetBaseValue or self.GetValue local bfm = self:GetBestFiremode(base) local erpm = valfunc(self, "RPM") local pbd = valfunc(self, "AutoBurst") and valfunc(self, "PostBurstDelay") or math.max(0.15, valfunc(self, "PostBurstDelay")) if bfm == 1 then erpm = math.min(erpm, 600) -- you can't click *that* fast elseif bfm < 0 then erpm = 60 / ((1 / (erpm / 60)) + (pbd / -bfm)) end local rps = valfunc(self, "RecoilPerShot") local rsp = valfunc(self, "RecoilSpreadPenalty") local rrt = self:GetRecoilResetTime(base) local rdr = valfunc(self, "RecoilDissipationRate") local dt = math.max(0, -rrt) local rbs = dt * rdr -- amount of recoil we can recover between shots even if fired ASAP local fss = valfunc(self, "RecoilFirstShotMult") local rmax = valfunc(self, "RecoilMaximum") local rk = math.abs(valfunc(self, "RecoilKick")) -- local rrec_s = math.Clamp(rdr / rps / rmax / 5, 0, 1) ^ 0.9 -- local mspr_s = math.Clamp(1 - rmax * rsp / 0.04, 0, 1) -- score = score + mspr_s * 20 -- [50] recoil kick over 1s local score_rk1 = 50 local shots = math.ceil(erpm / 60 * 1) score = score + math.Clamp(1 - (rk * shots * rrt - 3) / 12, 0, 1) * score_rk1 -- print("rk1", rk * shots * rrt, math.Clamp(1 - rk * shots * rrt / 15, 0, 1) * score_rk1) -- [50] bloom over 1s local score_sg = 50 if bfm < 0 then local rbb = math.max(0, pbd - rrt) * rdr -- recovery between bursts local rpb = -bfm * rps - (-bfm - 1) * rbs - rbb -- recoil per full burst score = score + math.Clamp(1 - (rpb * rsp * 2) / 0.03, 0, 1) ^ 1.5 * score_sg -- print("spb", rpb * rsp, math.Clamp(1 - (rpb * rsp * 3) / 0.04, 0, 1) ^ 2 * score_sg) else -- local sg = math.min(shots, math.ceil(rmax / rsp)) local sot = math.min(rmax, fss - rbs + (shots - 1) * (rps - rbs)) * rsp -- print("sot", sot, math.Clamp(1 - (sot - 0.01) / 0.03, 0, 1) ^ 0.75 * score_sg) score = score + math.Clamp(1 - (sot - 0.01) / 0.02, 0, 1) ^ 1.5 * score_sg end return score end, }, { Name = "rating.handling", Description = "rating.handling.desc", RatingFunction = function(self, base) local score = 0 -- [40] sprint score = score + math.Clamp(1 - (self:GetSprintToFireTime(base) - 0.15) / 0.5, 0, 1) * 40 -- [45] ads score = score + math.Clamp(1 - (self:GetAimDownSightsTime(base) - 0.15) / 0.5, 0, 1) * 45 -- [15] deploy score = score + math.Clamp(1 - (self:GetDeployTime(base) - 0.5) / 1.5, 0, 1) * 15 return score end, }, { Name = "rating.maneuvering", Description = "rating.maneuvering.desc", RatingFunction = function(self, base) local score = 0 local valfunc = base and self.GetBaseValue or self.GetValue -- [40] free aim + sway (if both are disabled, score goes to other 2) local bonus = 40 local freeaim_s = 1 if TacRP.ConVars["freeaim"]:GetBool() then if valfunc(self, "FreeAim") then freeaim_s = math.Clamp(1 - (valfunc(self, "FreeAimMaxAngle") - 2) / 8, 0, 1) ^ 0.8 end bonus = 0 end local sway_s = 1 if TacRP.ConVars["sway"]:GetBool() then sway_s = math.Clamp(1 - (valfunc(self, "Sway") - 0.75) / 2.25, 0, 1) bonus = 0 end if bonus == 0 then score = score + math.min(freeaim_s, sway_s) * 20 + math.max(freeaim_s, sway_s) * 20 end -- local diff = valfunc(self, "HipFireSpreadPenalty") / math.Clamp(self:GetBaseValue("Spread"), 0.015, 0.03) local hipspread = valfunc(self, "Spread") + valfunc(self, "HipFireSpreadPenalty") + valfunc(self, "MoveSpreadPenalty") -- [0] peeking -- score = score + math.Clamp(1 - (hipspread * valfunc(self, "PeekPenaltyFraction") - 0.01) / 0.015, 0, 1) * (10 + bonus * 0.25) -- [50] hip spread + spread score = score + math.Clamp(1 - (hipspread - 0.02) / 0.05, 0, 1) * (50 + bonus * 0.75) -- [10] mid-air spread score = score + math.Clamp(1 - (valfunc(self, "MidAirSpreadPenalty") ) / 0.1, 0, 1) * (10 + bonus * 0.25) return score end, }, { Name = "rating.mobility", Description = "rating.mobility.desc", RatingFunction = function(self, base) local score = 0 local valfunc = base and self.GetBaseValue or self.GetValue local ttt = TacRP.GetBalanceMode() == TacRP.BALANCE_TTT if ttt then -- [30] move score = score + math.Clamp((valfunc(self, "MoveSpeedMult") - 0.6) / 0.4, 0, 1) * 30 -- [25] sighted score = score + math.Clamp((valfunc(self, "SightedSpeedMult") - 0.2) / 0.8, 0, 1) * 25 -- [25] shooting score = score + math.Clamp((valfunc(self, "ShootingSpeedMult") - 0.2) / 0.8, 0, 1) * 25 -- [20] reload score = score + math.Clamp((valfunc(self, "ReloadSpeedMult") - 0.4) / 0.6, 0, 1) * 20 else -- [50] move score = score + math.Clamp((math.min(1, valfunc(self, "MoveSpeedMult")) - 0.6) / 0.4, 0, 1) ^ 2 * 50 -- [25] sighted score = score + math.Clamp((math.min(1, valfunc(self, "SightedSpeedMult")) - 0.3) / 0.7, 0, 1) * 25 -- [25] shooting score = score + math.Clamp((math.min(1, valfunc(self, "ShootingSpeedMult")) - 0.4) / 0.6, 0, 1) * 25 -- [-20] reload score = score - math.Clamp(1 - (math.min(1, valfunc(self, "ReloadSpeedMult")) - 0.4) / 0.6, 0, 1) * 20 end return score end, }, { Name = "rating.stability", Description = "rating.stability.desc", RatingFunction = function(self, base) local score = 0 local valfunc = base and self.GetBaseValue or self.GetValue -- [40] sway score = score + math.Clamp(1 - valfunc(self, "Sway") / 2.5, 0, 1) ^ 0.8 * 40 -- [60] sighted sway score = score + math.Clamp(1 - valfunc(self, "ScopedSway") / 1, 0, 1) ^ 1.5 * 60 -- blindfire sway -- score = score + math.Clamp(1 - valfunc(self, "BlindFireSway") / 2, 0, 1) * 10 return score end, }, } SWEP.StatDisplay = { -- { -- Name = "", -- Value = "", -- LowerIsBetter = false, -- AggregateFunction = nil, -- Unit = "" -- } { Name = "spacer.damage", Description = "spacer.damage.desc", Spacer = true, }, { Name = "stat.damage", Description = "stat.damage.desc", Value = "Damage_Max", AggregateFunction = function(self, base, val) if !(self:IsDamageConstant(false) and self:IsDamageConstant(true)) then return end -- local valfunc = base and self.GetBaseValue or self.GetValue -- return math.Round(val * valfunc(self, "Num"), 0) return math.floor(val) end, }, { Name = "stat.damage_max", Description = "stat.damage_max.desc", Value = "Damage_Max", AggregateFunction = function(self, base, val) if self:IsDamageConstant(false) and self:IsDamageConstant(true) then return end -- local valfunc = base and self.GetBaseValue or self.GetValue -- return math.Round(val * valfunc(self, "Num"), 0) return math.floor(val) end, }, { Name = "stat.damage_min", Description = "stat.damage_min.desc", Value = "Damage_Min", AggregateFunction = function(self, base, val) if self:IsDamageConstant(false) and self:IsDamageConstant(true) then return end -- local valfunc = base and self.GetBaseValue or self.GetValue -- return math.Round(val * valfunc(self, "Num"), 0) return math.floor(val) end, }, { Name = "stat.explosivedamage", Description = "stat.explosivedamage.desc", Value = "ExplosiveDamage", DefaultValue = 0, }, { Name = "stat.explosiveradius", Description = "stat.explosiveradius.desc", Value = "ExplosiveRadius", DefaultValue = 0, DisplayFunction = function(self, base, val) return self:RangeUnitize(val) end, }, { Name = "stat.damagemultnpc", Description = "stat.damagemultnpc.desc", Value = "DamageMultNPC", DefaultValue = 1, AggregateFunction = function(self, base, val) return math.Round(val * 100, 1) end, Unit = "%", }, { Name = "stat.num", Description = "stat.num.desc", Value = "Num", DefaultValue = 1, }, { Name = "stat.range_min", Description = "stat.range_min.desc", Value = "Range_Min", AggregateFunction = function(self, base, val) if self:IsDamageConstant(base) then return end return val end, DisplayFunction = function(self, base, val) if val == 0 then return "∞" end return self:RangeUnitize(val) end, }, { Name = "stat.range_max", Description = "stat.range_max.desc", Value = "Range_Max", AggregateFunction = function(self, base, val) if self:IsDamageConstant(base) then return end return val end, DisplayFunction = function(self, base, val) if val == 0 then return "∞" end return self:RangeUnitize(val) end, }, { Name = "stat.raw_dps", Description = "stat.raw_dps.desc", Value = "", AggregateFunction = function(self, base, val) local valfunc = base and self.GetBaseValue or self.GetValue local bfm = self:GetBestFiremode(base) local rrpm = self:GetRPM(base, bfm) local erpm = rrpm local pbd = valfunc(self, "PostBurstDelay") if bfm < 0 then erpm = 60 / ((1 / (rrpm / 60)) + (pbd / -bfm)) end local num = valfunc(self, "Num") local dmg = math.max(valfunc(self, "Damage_Max"), valfunc(self, "Damage_Min")) dmg = dmg + valfunc(self, "ExplosiveDamage") return math.Round(dmg * num * erpm / 60, 1) end, }, { Name = "stat.min_ttk", Description = "stat.min_ttk.desc", Value = "", Unit = "unit.second", LowerIsBetter = true, AggregateFunction = function(self, base, val) local valfunc = base and self.GetBaseValue or self.GetValue local bfm = self:GetBestFiremode(base) local rpm = self:GetRPM(base, bfm) local num = valfunc(self, "Num") local dmg = math.max(valfunc(self, "Damage_Max"), valfunc(self, "Damage_Min")) local stk = math.ceil(100 / (dmg * num)) local ttk = stk * (60 / rpm) if bfm < 0 and stk > -bfm then local pbd = valfunc(self, "PostBurstDelay") ttk = ttk + pbd * math.floor(stk / -bfm) end return math.Round(ttk, 2) end, }, { Name = "spacer.action", Description = "spacer.action.desc", Spacer = true, }, { Name = "stat.clipsize", Description = "stat.clipsize.desc", Value = "ClipSize", }, { Name = "stat.ammopershot", Description = "stat.ammopershot.desc", Value = "AmmoPerShot", DefaultValue = 1, }, { Name = "stat.rpm", Description = "stat.rpm.desc", Value = "RPM", Unit = "rpm", AggregateFunction = function(self, base, val) return math.Round(val, 0) end, }, { Name = "stat.rpm_burst", Description = "stat.rpm_burst.desc", Value = "RPMMultBurst", Unit = "rpm", AggregateFunction = function(self, base, val) if !self:HasFiremode(-1) then return end local valfunc = base and self.GetBaseValue or self.GetValue return math.Round(val * valfunc(self, "RPM"), 0) end, DefaultValue = 1, }, { Name = "stat.rpm_burst_peak", Description = "stat.rpm_burst_peak.desc", Value = "PostBurstDelay", Unit = "rpm", AggregateFunction = function(self, base, val) if !self:HasFiremode(-1) then return end local valfunc = base and self.GetBaseValue or self.GetValue local cfm = self:GetFiremodeBurstLength() local delay = 60 / ( valfunc(self, "RPM") * valfunc(self, "RPMMultBurst") ) -- delay local nerd = 0 nerd = nerd + (delay * (cfm-1)) nerd = nerd + math.max( delay, valfunc(self, "PostBurstDelay") ) nerd = nerd / cfm nerd = 60 / nerd return math.Round( nerd ) end, --DefaultValue = 0, }, { Name = "stat.rpm_semi", Description = "stat.rpm_semi.desc", Value = "RPMMultSemi", Unit = "rpm", AggregateFunction = function(self, base, val) if !self:HasFiremode(1) then return end local valfunc = base and self.GetBaseValue or self.GetValue return math.Round(val * valfunc(self, "RPM"), 0) end, DefaultValue = 1, }, { Name = "stat.shotstofail", Description = "stat.shotstofail.desc", AggregateFunction = function(self, base, val) return math.Round(1 / self:GetJamChance(base), 0) end, DisplayFunction = function(self, base, val) if val == 0 then return "∞" end return math.Round(1 / self:GetJamChance(base), 0) end, DefaultValue = 0, Value = "JamFactor", }, { Name = "stat.postburstdelay", Description = "stat.postburstdelay.desc", Value = "PostBurstDelay", AggregateFunction = function(self, base, val) if !self:HasFiremode(-1) then return end return math.Round(val, 2) end, Unit = "unit.second", LowerIsBetter = true, DefaultValue = 0, }, { Name = "stat.firemode", Description = "stat.firemode.desc", AggregateFunction = function(self, base, val) if !val then val = {base and self:GetTable()["Firemode"] or self:GetValue("Firemode")} end if #val == 1 then if val[1] == 2 then return "Auto" elseif val[1] == 1 then return "Semi" elseif val[1] < 0 then return (-val[1]) .. "-Burst" end else local tbl = table.Copy(val) table.sort(tbl, function(a, b) if a == 2 then return b == 2 elseif b == 2 then return a != 2 end return math.abs(a) <= math.abs(b) end) local str = "S-" for i = 1, #tbl do str = str .. (tbl[i] == 2 and "F" or math.abs(tbl[i])) .. (i < #tbl and "-" or "") end return str end return table.ToString(val) end, BetterFunction = function(self, old, new) if !old then old = {self:GetBaseValue("Firemode")} end if !new then new = {self:GetValue("Firemode")} end local oldbest, newbest = 0, 0 for i = 1, #old do local v = math.abs(old[i]) if math.abs(old[i]) > oldbest or old[i] == 2 then oldbest = (old[i] == 2 and math.huge) or v end end for i = 1, #new do local v = math.abs(new[i]) if v > newbest or new[i] == 2 then newbest = (new[i] == 2 and math.huge) or v end end if oldbest == newbest then return #old != #new , #old < #new else return true, oldbest < newbest end end, DifferenceFunction = function(self, orig, value) if !orig then orig = {self:GetBaseValue("Firemode")} end if !value then value = {self:GetValue("Firemode")} end local old_best = self:GetBestFiremode(true) local new_best = self:GetBestFiremode(false) if old_best == new_best then return end if new_best == 2 then return "+Auto" elseif old_best == 2 then return "-Auto" elseif new_best < 0 then return "+Burst" end end, Value = "Firemodes", -- HideIfSame = true, }, { Name = "stat.reloadtime", Description = "stat.reloadtime.desc", AggregateFunction = function(self, base, val) return math.Round(self:GetReloadTime(base), 2) end, Value = "ReloadTimeMult", LowerIsBetter = true, Unit = "unit.second", }, { Name = "spacer.ballistics", Description = "spacer.ballistics.desc", Spacer = true, }, { Name = "stat.spread", Description = "stat.spread.desc", AggregateFunction = function(self, base, val) return math.Round(math.deg(val) * 60, 1) end, Unit = "′", Value = "Spread", LowerIsBetter = true, }, { Name = "stat.muzzlevelocity", Description = "stat.muzzlevelocity.desc", AggregateFunction = function(self, base, val) return math.Round(self:GetMuzzleVelocity(base), 2) end, ConVarCheck = "tacrp_physbullet", Value = "MuzzleVelocity", LowerIsBetter = false, Unit = "unit.mps", }, { Name = "stat.penetration", Description = "stat.penetration.desc", Value = "Penetration", Unit = "\"" }, { Name = "stat.armorpenetration", Description = "stat.armorpenetration.desc", Value = "ArmorPenetration", AggregateFunction = function(self, base, val) return math.max(math.Round(val * 100, 1), 0) end, Unit = "%", }, { Name = "stat.armorbonus", Description = "stat.armorbonus.desc", Value = "ArmorBonus", AggregateFunction = function(self, base, val) return math.Round(val * 1, 2) end, Unit = "x", }, { Name = "spacer.recoilbloom", Description = "spacer.recoilbloom.desc", Spacer = true, }, { Name = "stat.recoilkick", Description = "stat.recoilkick.desc", Value = "RecoilKick", LowerIsBetter = true, }, { Name = "stat.recoilstability", Description = "stat.recoilstability.desc", Value = "RecoilStability", AggregateFunction = function(self, base, val) return math.Clamp(math.Round(val * 100), 0, 90) end, Unit = "%", LowerIsBetter = false, }, -- For use when bloom is modifying spread. (default) { Name = "stat.recoilspread", Description = "stat.recoilspread.desc", AggregateFunction = function(self, base, val) if self:UseAltRecoil(base) then return end return math.Round(math.deg(val) * 60, 1) end, Unit = "′", Value = "RecoilSpreadPenalty", LowerIsBetter = true, -- ConVarCheck = "tacrp_altrecoil", -- ConVarInvert = true, }, -- For use when in "Bloom Modifies Recoil" { Name = "stat.recoilspread2", Description = "stat.recoilspread2.desc", AggregateFunction = function(self, base, val) if !self:UseAltRecoil(base) then return end return math.Round(val * (base and self:GetBaseValue("RecoilAltMultiplier") or self:GetValue("RecoilAltMultiplier")), 2) end, Unit = nil, Value = "RecoilSpreadPenalty", LowerIsBetter = true, -- ConVarCheck = "tacrp_altrecoil", -- ConVarInvert = false, }, { Name = "stat.recoildissipation", Description = "stat.recoildissipation.desc", AggregateFunction = function(self, base, val) return math.Round(val, 2) --return math.Round(math.deg(val * (base and self:GetTable().RecoilSpreadPenalty or self:GetValue("RecoilSpreadPenalty"))), 1) end, Unit = "unit.persecond", Value = "RecoilDissipationRate", }, { Name = "stat.recoilresettime", Description = "stat.recoilresettime.desc", Value = "RecoilResetTime", LowerIsBetter = true, Unit = "s", }, { Name = "stat.recoilmaximum", Description = "stat.recoilmaximum.desc", Value = "RecoilMaximum", LowerIsBetter = true, }, { Name = "stat.recoilfirstshot", Description = "stat.recoilfirstshot.desc", Value = "RecoilFirstShotMult", AggregateFunction = function(self, base, val) return math.Round(val * (base and self:GetBaseValue("RecoilPerShot") or self:GetValue("RecoilPerShot")), 2) end, DefaultValue = 1, LowerIsBetter = true, }, { Name = "stat.recoilpershot", Description = "stat.recoilpershot.desc", AggregateFunction = function(self, base, val) return math.Round(val, 2) end, Unit = "x", Value = "RecoilPerShot", HideIfSame = true, LowerIsBetter = true, }, { Name = "stat.recoilcrouch", Description = "stat.recoilcrouch.desc", AggregateFunction = function(self, base, val) return math.min(100, math.Round(val * 100, 0)) end, Unit = "%", Value = "RecoilMultCrouch", LowerIsBetter = true, -- HideIfSame = true, }, { Name = "stat.recoilburst", Description = "stat.recoilburst.desc", Value = "RecoilMultBurst", Unit = "%", AggregateFunction = function(self, base, val) return math.min(100, math.Round(val * 100, 0)) end, LowerIsBetter = true, DefaultValue = 1, }, { Name = "stat.recoilsemi", Description = "stat.recoilsemi.desc", Value = "RecoilMultSemi", Unit = "%", AggregateFunction = function(self, base, val) return math.min(100, math.Round(val * 100, 0)) end, LowerIsBetter = true, DefaultValue = 1, }, { Name = "spacer.mobility", Description = "spacer.mobility.desc", Spacer = true, }, { Name = "stat.movespeed", Description = "stat.movespeed.desc", AggregateFunction = function(self, base, val) return math.min(100, math.Round(val * 100, 0)) end, Unit = "%", Value = "MoveSpeedMult", ConVarCheck = "tacrp_penalty_move" }, { Name = "stat.shootingspeed", Description = "stat.shootingspeed.desc", AggregateFunction = function(self, base, val) return math.min(100, math.Round(val * 100, 0)) end, Unit = "%", Value = "ShootingSpeedMult", ConVarCheck = "tacrp_penalty_firing" }, { Name = "stat.sightedspeed", Description = "stat.sightedspeed.desc", AggregateFunction = function(self, base, val) return math.min(100, math.Round(val * 100, 0)) end, Unit = "%", Value = "SightedSpeedMult", ValueCheck = "Scope", ConVarCheck = "tacrp_penalty_aiming" }, { Name = "stat.reloadspeed", Description = "stat.reloadspeed.desc", AggregateFunction = function(self, base, val) return math.min(100, math.Round(val * 100, 0)) end, Unit = "%", Value = "ReloadSpeedMult", DefaultValue = 1, ConVarCheck = "tacrp_penalty_reload" }, { Name = "stat.meleespeed", Description = "stat.meleespeed.desc", AggregateFunction = function(self, base, val) return math.min(100, math.Round(val * 100, 0)) end, Unit = "%", Value = "MeleeSpeedMult", DefaultValue = 1, ConVarCheck = "tacrp_penalty_melee" }, { Name = "spacer.handling", Description = "spacer.handling.desc", Spacer = true, }, { Name = "stat.sprinttofire", Description = "stat.sprinttofire.desc", Value = "SprintToFireTime", AggregateFunction = function(self, base, val) return math.Round(self:GetSprintToFireTime(base), 3) end, Unit = "unit.second", LowerIsBetter = true, }, { Name = "stat.aimdownsights", Description = "stat.aimdownsights.desc", Value = "AimDownSightsTime", AggregateFunction = function(self, base, val) return math.Round(self:GetAimDownSightsTime(base), 3) end, Unit = "unit.second", LowerIsBetter = true, ValueCheck = "Scope", }, { Name = "stat.deploytime", Description = "stat.deploytime.desc", AggregateFunction = function(self, base, val) return math.Round(self:GetDeployTime(base), 2) end, Value = "DeployTimeMult", LowerIsBetter = true, -- HideIfSame = true, Unit = "unit.second", }, { Name = "stat.holstertime", Description = "stat.holstertime.desc", AggregateFunction = function(self, base, val) return math.Round(self:CalcHolsterTime(base), 2) end, Value = "HolsterTimeMult", LowerIsBetter = true, Unit = "unit.second", ConVarCheck = "tacrp_holster", }, { Name = "spacer.maneuvering", Description = "spacer.maneuvering.desc", Spacer = true, }, { Name = "stat.freeaimangle", Description = "stat.freeaimangle.desc", Unit = "°", Value = "FreeAimMaxAngle", AggregateFunction = function(self, base, val) return math.max(0, math.Round(val, 1)) end, LowerIsBetter = true, -- HideIfSame = true, ConVarCheck = "tacrp_freeaim", }, { Name = "stat.midairspread", Description = "stat.midairspread.desc", AggregateFunction = function(self, base, val) return math.Round(math.deg(val), 1) end, Unit = "°", Value = "MidAirSpreadPenalty", LowerIsBetter = true, -- HideIfSame = true, }, { Name = "stat.hipfirespread", Description = "stat.hipfirespread.desc", AggregateFunction = function(self, base, val) return math.Round(math.deg(val), 1) end, Unit = "°", Value = "HipFireSpreadPenalty", LowerIsBetter = true, -- HideIfSame = true, }, { Name = "spacer.sway", Description = "spacer.sway.desc", Spacer = true, ConVarCheck = "tacrp_sway", }, { Name = "stat.sway", Description = "stat.sway.desc", Value = "Sway", LowerIsBetter = true, ConVarCheck = "tacrp_sway", }, { Name = "stat.scopedsway", Description = "stat.scopedsway.desc", Value = "ScopedSway", LowerIsBetter = true, ConVarCheck = "tacrp_sway", ValueCheck = "Scope", }, { Name = "stat.swaycrouch", Description = "stat.swaycrouch.desc", Value = "SwayCrouchMult", AggregateFunction = function(self, base, val) return math.min(100, math.Round(val * 100, 0)) end, Unit = "%", LowerIsBetter = true, -- HideIfSame = true, ConVarCheck = "tacrp_sway", }, { Name = "spacer.misc", Description = "spacer.misc.desc", Spacer = true, }, { Name = "stat.meleedamage", Description = "stat.meleedamage.desc", Value = "MeleeDamage", HideIfSame = false, }, { Name = "stat.peekpenalty", Description = "stat.peekpenalty.desc", Value = "PeekPenaltyFraction", AggregateFunction = function(self, base, val) return math.min(100, math.Round(val * 100, 0)) end, Unit = "%", LowerIsBetter = true, }, { Name = "stat.quickscope", Description = "stat.quickscope.desc", Value = "QuickScopeSpreadPenalty", AggregateFunction = function(self, base, val) return math.Round(math.deg(val) * 60, 1) end, Unit = "′", LowerIsBetter = true, }, { Name = "stat.vol_shoot", Description = "stat.vol_shoot.desc", Value = "Vol_Shoot", AggregateFunction = function(self, base, val) return math.Round(val, 1) end, Unit = "dB", LowerIsBetter = true, }, } SWEP.StatGroupsMelee = { { Name = "stat.damage", Description = "stat.damage.desc_melee", RatingFunction = function(self, base) local valfunc = base and self.GetBaseValue or self.GetValue return Lerp((valfunc(self, "MeleeDamage") - 10) / 50, 0, 100) end, }, { Name = "rating.meleeattacktime", Description = "rating.meleeattacktime.desc", RatingFunction = function(self, base) local valfunc = base and self.GetBaseValue or self.GetValue return Lerp(1 - (valfunc(self, "MeleeAttackTime") - 0.15) / 0.55, 0, 100) end, }, -- { -- Name = "Reach", -- Description = "Attack distance.", -- RatingFunction = function(self, base) -- local valfunc = base and self.GetBaseValue or self.GetValue -- return Lerp((valfunc(self, "MeleeRange") - 64) / 128, 0, 100) -- end, -- }, { Name = "stat.meleeperkstr", Description = "stat.meleeperkstr.desc", RatingFunction = function(self, base) local valfunc = base and self.GetBaseValue or self.GetValue return Lerp(valfunc(self, "MeleePerkStr"), 0, 100) end, }, { Name = "stat.meleeperkagi", Description = "stat.meleeperkagi.desc", RatingFunction = function(self, base) local valfunc = base and self.GetBaseValue or self.GetValue return Lerp(valfunc(self, "MeleePerkAgi"), 0, 100) end, }, { Name = "stat.meleeperkint", Description = "stat.meleeperkint.desc", RatingFunction = function(self, base) local valfunc = base and self.GetBaseValue or self.GetValue return Lerp(valfunc(self, "MeleePerkInt"), 0, 100) end, }, } SWEP.StatDisplayMelee = { { Name = "stat.damage", Description = "stat.damage.desc_melee", Value = "MeleeDamage", AggregateFunction = function(self, base, val) return math.floor(val * self:GetConfigDamageMultiplier()) end, }, { Name = "stat.meleeattacktime", Description = "stat.meleeattacktime.desc", Value = "MeleeAttackTime", Unit = "unit.second", LowerIsBetter = true, }, { Name = "stat.meleeattackmisstime", Description = "stat.meleeattackmisstime.desc", Value = "MeleeAttackMissTime", Unit = "unit.second", LowerIsBetter = true, }, { Name = "stat.meleerange", Description = "stat.meleerange.desc", Value = "MeleeRange", DisplayFunction = function(self, base, val) return self:RangeUnitize(val) end, }, { Name = "stat.meleedelay", Description = "stat.meleedelay.desc", Value = "MeleeDelay", Unit = "unit.second", LowerIsBetter = true, }, { Name = "stat.meleeperkstr", Description = "stat.meleeperkstr.desc", Value = "MeleePerkStr", AggregateFunction = function(self, base, val) return math.floor(val * 100) end, }, { Name = "stat.meleeperkagi", Description = "stat.meleeperkagi.desc", Value = "MeleePerkAgi", AggregateFunction = function(self, base, val) return math.floor(val * 100) end, }, { Name = "stat.meleeperkint", Description = "stat.meleeperkint.desc", Value = "MeleePerkInt", AggregateFunction = function(self, base, val) return math.floor(val * 100) end, }, { Name = "stat.melee2damage", Description = "stat.melee2damage.desc", Value = "Melee2Damage", ValueCheck = "HeavyAttack", AggregateFunction = function(self, base, val) return math.Round(self:GetHeavyAttackDamage(base) * self:GetConfigDamageMultiplier()) end, }, { Name = "stat.melee2attacktime", Description = "stat.melee2attacktime.desc", Value = "Melee2AttackTime", ValueCheck = "HeavyAttack", AggregateFunction = function(self, base, val) return math.Round(self:GetHeavyAttackTime(false, base), 2) end, Unit = "unit.second", LowerIsBetter = true, }, { Name = "stat.melee2attackmisstime", Description = "stat.melee2attackmisstime.desc", Value = "Melee2AttackMissTime", ValueCheck = "HeavyAttack", AggregateFunction = function(self, base, val) return math.Round(self:GetHeavyAttackTime(true, base), 2) end, Unit = "unit.second", LowerIsBetter = true, }, { Name = "stat.meleethrowdamage", Description = "stat.meleethrowdamage.desc", Value = "MeleeDamage", ValueCheck = "ThrowAttack", AggregateFunction = function(self, base, val) return math.floor(val * self:GetMeleePerkDamage(base) * self:GetConfigDamageMultiplier()) end, }, { Name = "stat.meleethrowvelocity", Description = "stat.meleethrowvelocity.desc", Value = "MeleeThrowForce", ValueCheck = "ThrowAttack", AggregateFunction = function(self, base, val) return math.Round(0.3048 * self:GetMeleePerkVelocity(base) / 12, 1) end, Unit = "unit.mps", }, { Name = "stat.meleethrowtime", Description = "stat.meleethrowtime.desc", Value = "MeleeAttackTime", ValueCheck = "ThrowAttack", AggregateFunction = function(self, base, val) return math.Round(val * 3 * self:GetMeleePerkCooldown(base), 2) end, Unit = "unit.second", LowerIsBetter = true, }, { Name = "stat.lifesteal", Description = "stat.lifesteal.desc", Value = "Lifesteal", DefaultValue = 0, AggregateFunction = function(self, base, val) return math.Round(val * 100, 2) end, Unit = "%", }, { Name = "stat.damagecharge", Description = "stat.damagecharge.desc", Value = "DamageCharge", DefaultValue = 0, AggregateFunction = function(self, base, val) return math.Round(val * 10000, 2) end, Unit = "%", }, }