Files
VnUtest/garrysmod/addons/tacrp/lua/weapons/tacrp_base/sh_aggregate.lua
2026-03-31 10:27:04 +03:00

1453 lines
49 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 = "%",
},
}