mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-17 05:43:46 +03:00
1203 lines
31 KiB
Lua
1203 lines
31 KiB
Lua
--[[
|
|
| This file was obtained through the combined efforts
|
|
| of Madbluntz & Plymouth Antiquarian Society.
|
|
|
|
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
|
| Maloy, DrPepper10 @ RIP, Atle!
|
|
|
|
|
| Visit for more: https://plymouth.thetwilightzone.ru/
|
|
--]]
|
|
|
|
|
|
-- Copyright (c) 2018-2020 TFA Base Devs
|
|
|
|
-- Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
-- of this software and associated documentation files (the "Software"), to deal
|
|
-- in the Software without restriction, including without limitation the rights
|
|
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
-- copies of the Software, and to permit persons to whom the Software is
|
|
-- furnished to do so, subject to the following conditions:
|
|
|
|
-- The above copyright notice and this permission notice shall be included in all
|
|
-- copies or substantial portions of the Software.
|
|
|
|
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
-- SOFTWARE.
|
|
|
|
TFA.RangeFalloffLUTStep = 0.01
|
|
TFA.RangeFalloffLUTStepInv = 1 / TFA.RangeFalloffLUTStep
|
|
|
|
SWEP.AmmoRangeTable = {
|
|
["SniperPenetratedRound"] = 2,
|
|
["SniperPenetratedBullet"] = 2,
|
|
["buckshot"] = 0.5,
|
|
["ar2"] = 1,
|
|
["smg1"] = 0.7,
|
|
["pistol"] = 0.33,
|
|
["def"] = 1
|
|
}
|
|
|
|
local IsValid = IsValid
|
|
|
|
function SWEP:AmmoRangeMultiplier()
|
|
return self.AmmoRangeTable[self.Primary_TFA.Ammo or "def"] or self.AmmoRangeTable["def"] or 1
|
|
end
|
|
|
|
function SWEP:MetersToUnits(x)
|
|
return x * 39.3701 * 4 / 3
|
|
end
|
|
|
|
function SWEP:GetLastSequenceString()
|
|
if not IsValid(self.OwnerViewModel) then return "" end
|
|
|
|
if self:GetLastSequence() < 0 then return "" end
|
|
return self.OwnerViewModel:GetSequenceName(self:GetLastSequence())
|
|
end
|
|
|
|
local cv_3dmode = GetConVar("cl_tfa_scope_sensitivity_3d")
|
|
|
|
SWEP.SensitivtyFunctions = {
|
|
[0] = function() return 1 end,
|
|
[1] = function(self, ...)
|
|
if self:GetStatL("Secondary.ScopeZoom") then
|
|
return TFA.CalculateSensitivtyScale(90 / self:GetStatL("Secondary.ScopeZoom"), self:GetStatL("Secondary.OwnerFOV"), self.Secondary_TFA.ScopeScreenScale or 0.392592592592592)
|
|
else
|
|
return self.SensitivtyFunctions[2](self, ...)
|
|
end
|
|
end,
|
|
[2] = function(self, ...)
|
|
if self:GetStatL("RTScopeFOV") then
|
|
return TFA.CalculateSensitivtyScale(self:GetStatL("RTScopeFOV"), self:GetStatL("Secondary.OwnerFOV"), self.Secondary_TFA.ScopeScreenScale or 0.392592592592592)
|
|
else
|
|
return self.SensitivtyFunctions[0](self, ...)
|
|
end
|
|
end,
|
|
[3] = function(self, ...)
|
|
if self:GetStatL("RTScopeFOV") then
|
|
return TFA.CalculateSensitivtyScale(self:GetStatL("RTScopeFOV"), self:GetStatL("Secondary.OwnerFOV"), 1)
|
|
else
|
|
return self.SensitivtyFunctions[0](self, ...)
|
|
end
|
|
end
|
|
}
|
|
|
|
function SWEP:Get3DSensitivity()
|
|
local f = self.SensitivtyFunctions[cv_3dmode:GetInt()]
|
|
|
|
return f(self)
|
|
end
|
|
|
|
function SWEP:GetSeed()
|
|
local sd = math.floor(self:Clip1() + self:Ammo1() + self:Clip2() + self:Ammo2() + self:GetLastActivity()) + self:GetNextIdleAnim() + self:GetNextPrimaryFire() + self:GetNextSecondaryFire()
|
|
|
|
return math.Round(sd)
|
|
end
|
|
|
|
function SWEP:GetSeedIrradical()
|
|
return math.floor(self:Clip1() + self:Ammo1() + self:Clip2() + self:Ammo2() + self:GetLastActivity()) + self:GetNextIdleAnim() + self:GetNextPrimaryFire() + self:GetNextSecondaryFire()
|
|
end
|
|
|
|
SWEP.SharedRandomValues = {}
|
|
local seed
|
|
|
|
--math.random equivalent
|
|
function SWEP:SharedRandom(min, max, id)
|
|
if min and not max then
|
|
max = min
|
|
min = 1
|
|
end
|
|
|
|
min = math.Round(min)
|
|
max = math.Round(max)
|
|
local key = (id or "Weapon") .. min .. max
|
|
seed = self:GetSeed()
|
|
local val = math.floor(util.SharedRandom(id or "Weapon", min, max + 1, seed))
|
|
|
|
if self.SharedRandomValues[key] and self.SharedRandomValues[key] == val then
|
|
if min < val and max > val then
|
|
math.randomseed(seed)
|
|
|
|
if (math.Rand(0, 1) < 0.5) then
|
|
math.randomseed(seed + 1)
|
|
val = math.random(min, val - 1)
|
|
else
|
|
math.randomseed(seed + 1)
|
|
val = math.random(val + 1, max)
|
|
end
|
|
elseif min < val then
|
|
math.randomseed(seed + 1)
|
|
val = math.random(min, val - 1)
|
|
elseif max > val then
|
|
math.randomseed(seed + 1)
|
|
val = math.random(val + 1, max)
|
|
end
|
|
end
|
|
|
|
if IsFirstTimePredicted() then
|
|
timer.Simple(0, function()
|
|
if IsValid(self) then
|
|
self.SharedRandomValues[key] = val
|
|
end
|
|
end)
|
|
end
|
|
|
|
return val
|
|
end
|
|
|
|
local oiv = nil
|
|
local holding_result_cached = false
|
|
local last_held_check = -1
|
|
local sp = game.SinglePlayer()
|
|
local slo, sqlo
|
|
local nm
|
|
|
|
--[[
|
|
local sqind
|
|
|
|
function SWEP:TranslateSequenceActivityTable( tbl )
|
|
if not self:VMIV() then return end
|
|
for k,v in pairs(tbl) do
|
|
if type(k) == "string" then
|
|
sqind = self.OwnerViewModel:GetSequenceActivity( self.OwnerViewModel:LookupSequence( k ) or -1 ) or -1
|
|
tbl[ sqind ] = tbl[sqind] or v
|
|
end
|
|
tbl[k] = nil
|
|
end
|
|
end
|
|
]]
|
|
--
|
|
--, seq )
|
|
function SWEP:GetActivityLengthRaw(tanim, status, animType)
|
|
local vm = self:VMIVNPC()
|
|
if not vm then return 0 end
|
|
|
|
if tanim == nil then
|
|
-- we already track last sequence so, we can account sequence
|
|
tanim = self:GetLastSequence()
|
|
animType = TFA.Enum.ANIMATION_SEQ
|
|
end
|
|
|
|
if tanim < 0 then return 0 end
|
|
|
|
if animType == nil or animType == TFA.Enum.ANIMATION_ACT then
|
|
nm = vm:GetSequenceName(vm:SelectWeightedSequenceSeeded(tanim, self:GetSeedIrradical()))
|
|
else
|
|
nm = vm:GetSequenceName(tanim)
|
|
end
|
|
|
|
local sqlen
|
|
|
|
if animType == TFA.Enum.ANIMATION_SEQ then
|
|
sqlen = vm:SequenceDuration(tanim)
|
|
elseif tanim == vm:GetSequenceActivity(vm:GetSequence()) then
|
|
sqlen = vm:SequenceDuration(vm:GetSequence())
|
|
else
|
|
sqlen = vm:SequenceDuration(vm:SelectWeightedSequenceSeeded(math.max(tanim or 1, 1), self:GetSeedIrradical()))
|
|
end
|
|
|
|
slo = self:GetStatL("StatusLengthOverride." .. nm) or self:GetStatL("StatusLengthOverride." .. (tanim or "0"))
|
|
sqlo = self:GetStatL("SequenceLengthOverride." .. nm) or self:GetStatL("SequenceLengthOverride." .. (tanim or "0"))
|
|
|
|
if status and slo then
|
|
sqlen = slo
|
|
elseif sqlo then
|
|
sqlen = sqlo
|
|
end
|
|
|
|
return sqlen
|
|
end
|
|
|
|
function SWEP:GetActivityLength(tanim, status, animType)
|
|
if not self:VMIVNPC() then return 0 end
|
|
local sqlen = self:GetActivityLengthRaw(tanim, status, animType)
|
|
if sqlen <= 0 then return 0 end
|
|
return sqlen / self:GetAnimationRate(tanim)
|
|
end
|
|
|
|
function SWEP:GetHolding()
|
|
if CurTime() > last_held_check + 0.2 then
|
|
last_held_check = CurTime()
|
|
holding_result_cached = nil
|
|
end
|
|
|
|
if holding_result_cached == nil then
|
|
holding_result_cached = false
|
|
|
|
if not IsValid(self:GetOwner()) or not self:GetOwner():IsPlayer() then
|
|
holding_result_cached = false
|
|
|
|
return false
|
|
end
|
|
|
|
local ent = self:GetOwner():GetNW2Entity("LastHeldEntity")
|
|
|
|
if not IsValid(ent) then
|
|
holding_result_cached = false
|
|
|
|
return false
|
|
end
|
|
|
|
if ent.IsPlayerHolding then
|
|
ent:SetNW2Bool("PlayerHolding", ent:IsPlayerHolding())
|
|
end
|
|
|
|
if ent:GetNW2Bool("PlayerHolding") then
|
|
holding_result_cached = true
|
|
|
|
return true
|
|
end
|
|
end
|
|
|
|
return holding_result_cached
|
|
end
|
|
|
|
function SWEP:CanInterruptShooting()
|
|
return self:GetStatL("Primary.RPM") > 160 and not self:GetStatL("BoltAction") and not self:GetStatL("BoltAction_Forced")
|
|
end
|
|
|
|
function SWEP:ReloadCV()
|
|
if rlcv then
|
|
if (not rlcv:GetBool()) and (not self.Primary_TFA.ClipSize_PreEdit) then
|
|
self.Primary_TFA.ClipSize_PreEdit = self.Primary_TFA.ClipSize
|
|
self.Primary_TFA.ClipSize = -1
|
|
self:ClearStatCache()
|
|
elseif rlcv:GetBool() and self.Primary_TFA.ClipSize_PreEdit then
|
|
self.Primary_TFA.ClipSize = self.Primary_TFA.ClipSize_PreEdit
|
|
self.Primary_TFA.ClipSize_PreEdit = nil
|
|
self:ClearStatCache()
|
|
end
|
|
end
|
|
end
|
|
|
|
function SWEP:OwnerIsValid()
|
|
if oiv == nil then
|
|
oiv = IsValid(self:GetOwner())
|
|
end
|
|
|
|
return oiv
|
|
end
|
|
|
|
function SWEP:NullifyOIV()
|
|
if oiv ~= nil then
|
|
self:GetHolding()
|
|
oiv = nil
|
|
end
|
|
|
|
return self:VMIV()
|
|
end
|
|
|
|
function SWEP:VMIVNPC()
|
|
local ply = self:GetOwner()
|
|
|
|
if ply:IsPlayer() then return self:VMIV() end
|
|
|
|
if ply:IsNPC() then
|
|
return self
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
function SWEP:VMIV()
|
|
local owent = self:GetOwner()
|
|
|
|
if not IsValid(self.OwnerViewModel) then
|
|
if IsValid(owent) and owent.GetViewModel then
|
|
self.OwnerViewModel = owent:GetViewModel()
|
|
end
|
|
|
|
return false
|
|
else
|
|
if not IsValid(owent) or not owent.GetViewModel then
|
|
self.OwnerViewModel = nil
|
|
|
|
return false
|
|
end
|
|
|
|
return self.OwnerViewModel
|
|
end
|
|
end
|
|
|
|
function SWEP:CanChamber()
|
|
if self.C_CanChamber ~= nil then
|
|
return self.C_CanChamber
|
|
else
|
|
self.C_CanChamber = not self:GetStatL("BoltAction") and not self:GetStatL("LoopedReload") and not self.Revolver and not self:GetStatL("Primary.DisableChambering")
|
|
|
|
return self.C_CanChamber
|
|
end
|
|
end
|
|
|
|
function SWEP:GetPrimaryClipSize(calc)
|
|
local targetclip = self:GetStatL("Primary.ClipSize")
|
|
|
|
if self:CanChamber() and not (calc and self:Clip1() <= 0) then
|
|
targetclip = targetclip + (self:GetStatL("IsAkimbo") and 2 or 1)
|
|
end
|
|
|
|
return math.max(targetclip, -1)
|
|
end
|
|
|
|
function SWEP:GetPrimaryClipSizeForReload(calc)
|
|
local targetclip = self:GetStatL("Primary.ClipSize")
|
|
|
|
if self:CanChamber() and not (calc and self:Clip1() <= 0) and not self:IsJammed() then
|
|
targetclip = targetclip + (self:GetStatL("IsAkimbo") and 2 or 1)
|
|
end
|
|
|
|
return math.max(targetclip, -1)
|
|
end
|
|
|
|
function SWEP:GetSecondaryClipSize(calc)
|
|
local targetclip = self:GetStatL("Secondary.ClipSize")
|
|
|
|
return math.max(targetclip, -1)
|
|
end
|
|
|
|
--[[
|
|
|
|
function SWEP:GetPrimaryAmmoType()
|
|
return self:GetStatL( "Primary.Ammo" ) or ""
|
|
end
|
|
|
|
function SWEP:GetPrimaryAmmoTypeC()
|
|
return self:GetStatL( "Primary.Ammo" ) or self:GetPrimaryAmmoType()
|
|
end
|
|
|
|
function SWEP:Ammo1()
|
|
return self:GetOwner():GetAmmoCount( self:GetPrimaryAmmoTypeC() or 0 )
|
|
end
|
|
|
|
function SWEP:GetSecondaryAmmoType()
|
|
return self:GetStatL( "Secondary.Ammo" ) or ""
|
|
end
|
|
|
|
function SWEP:GetSecondaryAmmoTypeC()
|
|
return self:GetStatL( "Secondary.Ammo" ) or self:GetSecondaryAmmoType()
|
|
end
|
|
|
|
function SWEP:Ammo2()
|
|
return self:GetOwner():GetAmmoCount( self:GetSecondaryAmmoTypeC() or -1 )
|
|
end
|
|
|
|
]]
|
|
--
|
|
local at
|
|
|
|
function SWEP:GetPrimaryAmmoTypeC()
|
|
at = self:GetStatL("Primary.Ammo")
|
|
|
|
if at and at ~= self.Primary_TFA.Ammo then
|
|
return at
|
|
elseif self.GetPrimaryAmmoTypeOld then
|
|
return self:GetPrimaryAmmoTypeOld()
|
|
else
|
|
return self:GetPrimaryAmmoType()
|
|
end
|
|
end
|
|
|
|
function SWEP:GetSecondaryAmmoTypeC()
|
|
at = self:GetStatL("Secondary.Ammo")
|
|
|
|
if at and at ~= self.Secondary_TFA.Ammo then
|
|
return at
|
|
elseif self.GetSecondaryAmmoTypeOld then
|
|
return self:GetSecondaryAmmoTypeOld()
|
|
else
|
|
return self:GetSecondaryAmmoType()
|
|
end
|
|
end
|
|
|
|
function SWEP:Ammo1()
|
|
if not self:GetOwner():IsValid() then return false end
|
|
if self:GetOwner():IsNPC() then return 9999 end
|
|
|
|
return self:GetOwner():GetAmmoCount(self:GetPrimaryAmmoTypeC() or 0)
|
|
end
|
|
|
|
function SWEP:Ammo2()
|
|
if not self:GetOwner():IsValid() then return false end
|
|
if self:GetOwner():IsNPC() then return 9999 end
|
|
|
|
return self:GetOwner():GetAmmoCount(self:GetSecondaryAmmoTypeC() or -1)
|
|
end
|
|
|
|
function SWEP:TakePrimaryAmmo(num, pool)
|
|
-- Doesn't use clips
|
|
if self:GetStatL("Primary.ClipSize") < 0 or pool then
|
|
if (self:Ammo1() <= 0) then return end
|
|
if not self:GetOwner():IsPlayer() then return end
|
|
self:GetOwner():RemoveAmmo(math.min(self:Ammo1(), num), self:GetPrimaryAmmoTypeC())
|
|
|
|
return
|
|
end
|
|
|
|
self:SetClip1(math.max(self:Clip1() - num, 0))
|
|
end
|
|
|
|
function SWEP:TakeSecondaryAmmo(num, pool)
|
|
-- Doesn't use clips
|
|
if self:GetStatL("Secondary.ClipSize") < 0 or pool then
|
|
if (self:Ammo2() <= 0) then return end
|
|
if not self:GetOwner():IsPlayer() then return end
|
|
self:GetOwner():RemoveAmmo(math.min(self:Ammo2(), num), self:GetSecondaryAmmoTypeC())
|
|
|
|
return
|
|
end
|
|
|
|
self:SetClip2(math.max(self:Clip2() - num, 0))
|
|
end
|
|
|
|
function SWEP:IsEmpty1()
|
|
return self:GetStatL("Primary.ClipSize") > 0 and self:Clip1() == 0 or
|
|
self:GetStatL("Primary.ClipSize") <= 0 and self:Ammo1() == 0
|
|
end
|
|
|
|
function SWEP:IsEmpty2()
|
|
return self:GetStatL("Secondary.ClipSize") > 0 and self:Clip2() == 0 or
|
|
self:GetStatL("Secondary.ClipSize") <= 0 and self:Ammo2() == 0
|
|
end
|
|
|
|
SWEP.TakeAmmo1 = SWEP.TakePrimaryAmmo
|
|
SWEP.TakeAmmo2 = SWEP.TakeSecondaryAmmo
|
|
|
|
function SWEP:GetFireDelay()
|
|
if self:GetMaxBurst() > 1 and self:GetStatL("Primary.RPM_Burst") and self:GetStatL("Primary.RPM_Burst") > 0 then
|
|
return 60 / self:GetStatL("Primary.RPM_Burst")
|
|
elseif self:GetStatL("Primary.RPM_Semi") and not self.Primary_TFA.Automatic and self:GetStatL("Primary.RPM_Semi") and self:GetStatL("Primary.RPM_Semi") > 0 then
|
|
return 60 / self:GetStatL("Primary.RPM_Semi")
|
|
elseif self:GetStatL("Primary.RPM") and self:GetStatL("Primary.RPM") > 0 then
|
|
return 60 / self:GetStatL("Primary.RPM")
|
|
else
|
|
return self:GetStatL("Primary.Delay") or 0.1
|
|
end
|
|
end
|
|
|
|
function SWEP:GetBurstDelay(bur)
|
|
if not bur then
|
|
bur = self:GetMaxBurst()
|
|
end
|
|
|
|
if bur <= 1 then return 0 end
|
|
if self:GetStatL("Primary.BurstDelay") then return self:GetStatL("Primary.BurstDelay") end
|
|
|
|
return self:GetFireDelay() * 3
|
|
end
|
|
|
|
local tickrate = engine.TickInterval()
|
|
|
|
function SWEP:GetNextCorrectedPrimaryFire(delay)
|
|
local nextfire = self:GetNextPrimaryFire()
|
|
local delta = CurTime() - nextfire
|
|
|
|
if delta < 0 or delta > tickrate then
|
|
nextfire = CurTime()
|
|
end
|
|
|
|
return nextfire + delay
|
|
end
|
|
|
|
function SWEP:GetNextCorrectedSecondaryFire(delay)
|
|
local nextfire = self:GetNextSecondaryFire()
|
|
local delta = CurTime() - nextfire
|
|
|
|
if delta < 0 or delta > tickrate then
|
|
nextfire = CurTime()
|
|
end
|
|
|
|
return nextfire + delay
|
|
end
|
|
|
|
--[[
|
|
Function Name: IsSafety
|
|
Syntax: self:IsSafety().
|
|
Returns: Are we in safety firemode.
|
|
Notes: Non.
|
|
Purpose: Utility
|
|
]]
|
|
--
|
|
function SWEP:IsSafety()
|
|
if not self:GetStatL("FireModes") then return false end
|
|
local fm = self:GetStatL("FireModes")[self:GetFireMode()]
|
|
local fmn = string.lower(fm and fm or self:GetStatL("FireModes")[1])
|
|
|
|
if fmn == "safe" or fmn == "holster" then
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
function SWEP:UpdateMuzzleAttachment()
|
|
if not self:VMIV() then return end
|
|
local vm = self.OwnerViewModel
|
|
if not IsValid(vm) then return end
|
|
self.MuzzleAttachmentRaw = nil
|
|
|
|
if not self.MuzzleAttachmentSilenced then
|
|
self.MuzzleAttachmentSilenced = (vm:LookupAttachment("muzzle_silenced") <= 0) and self.MuzzleAttachment or "muzzle_silenced"
|
|
end
|
|
|
|
if self:GetSilenced() and self.MuzzleAttachmentSilenced then
|
|
self.MuzzleAttachmentRaw = vm:LookupAttachment(self.MuzzleAttachmentSilenced)
|
|
|
|
if not self.MuzzleAttachmentRaw or self.MuzzleAttachmentRaw <= 0 then
|
|
self.MuzzleAttachmentRaw = nil
|
|
end
|
|
end
|
|
|
|
if not self.MuzzleAttachmentRaw and self.MuzzleAttachment then
|
|
self.MuzzleAttachmentRaw = vm:LookupAttachment(self.MuzzleAttachment)
|
|
|
|
if not self.MuzzleAttachmentRaw or self.MuzzleAttachmentRaw <= 0 then
|
|
self.MuzzleAttachmentRaw = 1
|
|
end
|
|
end
|
|
|
|
local mzm = self:GetStatL("MuzzleAttachmentMod", 0)
|
|
|
|
if mzm then
|
|
if isstring(mzm) then
|
|
self.MuzzleAttachmentRaw = vm:LookupAttachment(mzm)
|
|
elseif mzm > 0 then
|
|
self.MuzzleAttachmentRaw = mzm
|
|
end
|
|
end
|
|
end
|
|
|
|
function SWEP:UpdateConDamage()
|
|
if not IsValid(self) then return end
|
|
|
|
if not self.DamageConVar then
|
|
self.DamageConVar = GetConVar("sv_tfa_damage_multiplier")
|
|
end
|
|
|
|
if self.DamageConVar and self.DamageConVar.GetFloat then
|
|
self.ConDamageMultiplier = self.DamageConVar:GetFloat()
|
|
end
|
|
end
|
|
|
|
--[[
|
|
Function Name: IsCurrentlyScoped
|
|
Syntax: self:IsCurrentlyScoped().
|
|
Returns: Is the player scoped in enough to display the overlay? true/false, returns a boolean.
|
|
Notes: Change SWEP.ScopeOverlayThreshold to change when the overlay is displayed.
|
|
Purpose: Utility
|
|
]]
|
|
--
|
|
function SWEP:IsCurrentlyScoped()
|
|
return (self:GetIronSightsProgress() > self:GetStatL("ScopeOverlayThreshold")) and self:GetStatL("Scoped")
|
|
end
|
|
|
|
--[[
|
|
Function Name: IsCurrently3DScoped
|
|
Syntax: self:IsCurrently3DScoped().
|
|
Returns: Is player aiming down the sights while having a RT-enabled scope equipped?
|
|
Notes:
|
|
Purpose: Utility
|
|
]]
|
|
--
|
|
function SWEP:IsCurrently3DScoped()
|
|
return (self:GetStatL("RTDrawEnabled") or self.RTCode ~= nil) and self:GetIronSights()
|
|
end
|
|
|
|
--[[
|
|
Function Name: IsHidden
|
|
Syntax: self:IsHidden().
|
|
Returns: Should we hide self?.
|
|
Notes:
|
|
Purpose: Utility
|
|
]]
|
|
--
|
|
function SWEP:GetHidden()
|
|
if not self:VMIV() then return true end
|
|
if self.DrawViewModel ~= nil and not self.DrawViewModel then return true end
|
|
if self.ShowViewModel ~= nil and not self.ShowViewModel then return true end
|
|
if self:GetHolding() then return true end
|
|
|
|
return self:IsCurrentlyScoped()
|
|
end
|
|
|
|
--[[
|
|
Function Name: IsFirstPerson
|
|
Syntax: self:IsFirstPerson().
|
|
Returns: Is the owner in first person.
|
|
Notes: Broken in singplayer because gary.
|
|
Purpose: Utility
|
|
]]
|
|
--
|
|
function SWEP:IsFirstPerson()
|
|
if not IsValid(self) or not self:OwnerIsValid() then return false end
|
|
if self:GetOwner():IsNPC() then return false end
|
|
if CLIENT and (not game.SinglePlayer()) and self:GetOwner() ~= GetViewEntity() then return false end
|
|
if sp and SERVER then return not self:GetOwner().TFASDLP end
|
|
if self:GetOwner().ShouldDrawLocalPlayer and self:GetOwner():ShouldDrawLocalPlayer() then return false end
|
|
if LocalPlayer and hook.Call("ShouldDrawLocalPlayer", GAMEMODE, self:GetOwner()) then return false end
|
|
|
|
return true
|
|
end
|
|
|
|
--[[
|
|
Function Name: GetMuzzlePos
|
|
Syntax: self:GetMuzzlePos( hacky workaround that doesn't work anyways ).
|
|
Returns: The AngPos for the muzzle attachment.
|
|
Notes: Defaults to the first attachment, and uses GetFPMuzzleAttachment
|
|
Purpose: Utility
|
|
]]
|
|
--
|
|
local fp
|
|
|
|
function SWEP:GetMuzzleAttachment()
|
|
local vmod = self.OwnerViewModel
|
|
local att = math.max(1, self.MuzzleAttachmentRaw or (sp and vmod or self):LookupAttachment(self.MuzzleAttachment))
|
|
|
|
if self:GetStatL("IsAkimbo") then
|
|
att = 1 + self:GetAnimCycle()
|
|
end
|
|
|
|
return att
|
|
end
|
|
|
|
function SWEP:GetMuzzlePos(ignorepos)
|
|
fp = self:IsFirstPerson()
|
|
local vm = self.OwnerViewModel
|
|
|
|
if not IsValid(vm) then
|
|
vm = self
|
|
end
|
|
|
|
-- Avoid returning strings inside MuzzleAttachmentMod, since this would decrease performance
|
|
-- Better call :UpdateMuzzleAttachment() or return number in MuzzleAttachmentMod
|
|
local obj = self:GetStatL("MuzzleAttachmentMod") or self.MuzzleAttachmentRaw or vm:LookupAttachment(self.MuzzleAttachment)
|
|
|
|
if type(obj) == "string" then
|
|
obj = tonumber(obj) or vm:LookupAttachment(obj)
|
|
end
|
|
|
|
local muzzlepos
|
|
obj = math.Clamp(obj or 1, 1, 128)
|
|
|
|
if fp then
|
|
muzzlepos = vm:GetAttachment(obj)
|
|
else
|
|
muzzlepos = self:GetAttachment(obj)
|
|
end
|
|
|
|
return muzzlepos
|
|
end
|
|
|
|
function SWEP:FindEvenBurstNumber()
|
|
local burstOverride = self:GetStatL("BurstFireCount")
|
|
|
|
if burstOverride then
|
|
return burstOverride
|
|
end
|
|
|
|
if (self:GetStatL("Primary.ClipSize") % 3 == 0) then
|
|
return 3
|
|
elseif (self:GetStatL("Primary.ClipSize") % 2 == 0) then
|
|
return 2
|
|
else
|
|
local i = 4
|
|
|
|
while i <= 7 do
|
|
if self:GetStatL("Primary.ClipSize") % i == 0 then return i end
|
|
i = i + 1
|
|
end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
function SWEP:GetFireModeName()
|
|
local fm = self:GetFireMode()
|
|
local fmn = string.lower(self:GetStatL("FireModes")[fm])
|
|
if fmn == "safe" or fmn == "holster" then return language.GetPhrase("tfa.firemode.safe") end
|
|
if self:GetStatL("FireModeName") then return language.GetPhrase(self:GetStatL("FireModeName")) end
|
|
if fmn == "auto" or fmn == "automatic" then return language.GetPhrase("tfa.firemode.auto") end
|
|
|
|
if fmn == "semi" or fmn == "single" then
|
|
if self:GetStatL("Revolver") then
|
|
if (self:GetStatL("BoltAction")) then
|
|
return language.GetPhrase("tfa.firemode.single")
|
|
else
|
|
return language.GetPhrase("tfa.firemode.revolver")
|
|
end
|
|
else
|
|
if (self:GetStatL("BoltAction")) then
|
|
return language.GetPhrase("tfa.firemode.bolt")
|
|
else
|
|
if self:GetStatL("LoopedReload") and self:GetStatL("Primary.RPM") < 250 then
|
|
return language.GetPhrase("tfa.firemode.pump")
|
|
else
|
|
return language.GetPhrase("tfa.firemode.semi")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local bpos = string.find(fmn, "burst")
|
|
if bpos then return language.GetPhrase("tfa.firemode.burst"):format(string.sub(fmn, 1, bpos - 1)) end
|
|
|
|
return ""
|
|
end
|
|
|
|
SWEP.BurstCountCache = {}
|
|
|
|
function SWEP:GetMaxBurst()
|
|
local fm = self:GetFireMode()
|
|
|
|
if not self.BurstCountCache[fm] then
|
|
local fmt = self:GetStatL("FireModes")
|
|
local fmn = string.lower(fmt[fm])
|
|
local bpos = string.find(fmn, "burst")
|
|
|
|
if bpos then
|
|
self.BurstCountCache[fm] = tonumber(string.sub(fmn, 1, bpos - 1))
|
|
else
|
|
self.BurstCountCache[fm] = 1
|
|
end
|
|
end
|
|
|
|
return self.BurstCountCache[fm]
|
|
end
|
|
|
|
--[[
|
|
Function Name: CycleFireMode
|
|
Syntax: self:CycleFireMode()
|
|
Returns: Nothing.
|
|
Notes: Cycles to next firemode.
|
|
Purpose: Feature
|
|
]]
|
|
--
|
|
local l_CT = CurTime
|
|
|
|
SWEP.FireModesAutomatic = {
|
|
["Automatic"] = true,
|
|
["Auto"] = true,
|
|
}
|
|
|
|
SWEP.FireModeSound = Sound("Weapon_AR2.Empty") -- firemode toggle sound
|
|
|
|
function SWEP:CycleFireMode()
|
|
local ct = l_CT()
|
|
local fm = self:GetFireMode()
|
|
fm = fm + 1
|
|
|
|
if fm >= #self:GetStatL("FireModes") then
|
|
fm = 1
|
|
end
|
|
|
|
self:SetFireMode(fm)
|
|
local success, tanim, ttype = self:ChooseROFAnim()
|
|
|
|
if success then
|
|
self:SetNextPrimaryFire(ct + self:GetActivityLength(tanim, false, ttype))
|
|
else
|
|
self:EmitSound(self:GetStatL("FireModeSound"))
|
|
self:SetNextPrimaryFire(ct + math.max(self:GetFireDelay(), 0.25))
|
|
end
|
|
|
|
self.BurstCount = 0
|
|
self:SetIsCyclingSafety(false)
|
|
self:SetStatus(TFA.Enum.STATUS_FIREMODE, self:GetNextPrimaryFire())
|
|
|
|
self.Primary.Automatic = self:GetStatL("FireModesAutomatic." .. self:GetStatL("FireModes." .. fm)) ~= nil
|
|
self.Primary_TFA.Automatic = self.Primary.Automatic
|
|
end
|
|
|
|
--[[
|
|
Function Name: CycleSafety
|
|
Syntax: self:CycleSafety()
|
|
Returns: Nothing.
|
|
Notes: Toggles safety
|
|
Purpose: Feature
|
|
]]
|
|
--
|
|
function SWEP:CycleSafety()
|
|
local ct = l_CT()
|
|
local fm = self:GetFireMode()
|
|
local fmt = self:GetStatL("FireModes")
|
|
|
|
self.BurstCount = 0
|
|
self:SetIsCyclingSafety(true)
|
|
self:SetIronSightsRaw(false)
|
|
|
|
if fm ~= #fmt then
|
|
self.LastFireMode = fm
|
|
self:SetFireMode(#fmt)
|
|
else
|
|
self:SetFireMode(self.LastFireMode or 1)
|
|
end
|
|
|
|
local success, tanim, ttype = self:ChooseROFAnim()
|
|
|
|
if success then
|
|
self:SetSafetyCycleAnimated(true)
|
|
self:SetNextPrimaryFire(ct + self:GetActivityLength(tanim, false, ttype))
|
|
else
|
|
self:SetSafetyCycleAnimated(false)
|
|
self:EmitSound(self:GetStatL("FireModeSound"))
|
|
self:SetNextPrimaryFire(ct + math.max(self:GetFireDelay(), 0.25))
|
|
end
|
|
|
|
self:SetStatus(TFA.Enum.STATUS_FIREMODE, self:GetNextPrimaryFire())
|
|
|
|
if self:IsSafety() then
|
|
self.Primary.Automatic = false
|
|
self.Primary_TFA.Automatic = false
|
|
else
|
|
self.Primary.Automatic = self:GetStatL("FireModesAutomatic." .. self:GetStatL("FireModes." .. self:GetFireMode())) ~= nil
|
|
self.Primary_TFA.Automatic = self.Primary.Automatic
|
|
end
|
|
end
|
|
|
|
--[[
|
|
Function Name: 1FireMode
|
|
Syntax: self:ProcessFireMode()
|
|
Returns: Nothing.
|
|
Notes: Processes fire mode changing and whether the swep is auto or not.
|
|
Purpose: Feature
|
|
]]
|
|
--
|
|
function SWEP:ProcessFireMode()
|
|
local owner = self.GetOwner and self:GetOwner()
|
|
if !owner or owner and !IsValid(owner) then return end
|
|
|
|
if owner:IsNPC() then return end
|
|
|
|
if owner.GetInfoNum and owner:GetInfoNum("cl_tfa_keys_firemode", 0) > 0 then
|
|
return
|
|
end
|
|
|
|
if self:OwnerIsValid() and self:KeyPressed(IN_RELOAD) and self:KeyDown(IN_USE) and self:GetStatus() == TFA.Enum.STATUS_IDLE and (SERVER or not sp) then
|
|
if self:GetStatL("SelectiveFire") and not self:KeyDown(IN_SPEED) then
|
|
self:CycleFireMode()
|
|
elseif owner:KeyDown(IN_SPEED) then
|
|
self:CycleSafety()
|
|
end
|
|
end
|
|
end
|
|
|
|
--[[
|
|
Function Name: Unload
|
|
Syntax: self:Unload()
|
|
Returns: Nothing.
|
|
Notes: Returns Clip1 ammo to reserve.
|
|
Purpose: Utility
|
|
]]
|
|
--
|
|
function SWEP:Unload()
|
|
local amm = self:Clip1()
|
|
self:SetClip1(0)
|
|
|
|
if self.OwnerIsValid and self:OwnerIsValid() and self.Owner.GiveAmmo then
|
|
self:GetOwner():GiveAmmo(amm, self:GetPrimaryAmmoType(), true)
|
|
end
|
|
end
|
|
|
|
--[[
|
|
Function Name: Unload
|
|
Syntax: self:Unload()
|
|
Returns: Nothing.
|
|
Notes: Returns Clip1 ammo to reserve.
|
|
Purpose: Utility
|
|
]]
|
|
--
|
|
function SWEP:Unload2()
|
|
local amm = self:Clip2()
|
|
self:SetClip2(0)
|
|
|
|
if self.OwnerIsValid and self:OwnerIsValid() and self.Owner.GiveAmmo then
|
|
self:GetOwner():GiveAmmo(amm, self:GetSecondaryAmmoType(), true)
|
|
end
|
|
end
|
|
|
|
local penetration_hitmarker_cvar = GetConVar("sv_tfa_penetration_hitmarker")
|
|
|
|
function SWEP:SendHitMarker(ply, traceres, dmginfo)
|
|
if CLIENT or not penetration_hitmarker_cvar:GetBool() then return end
|
|
if not IsValid(ply) or not ply:IsPlayer() then return end
|
|
|
|
local hm3d = ply:GetInfoNum("cl_tfa_hud_hitmarker_3d_all", 0) > 0
|
|
local hm3d_sg = ply:GetInfoNum("cl_tfa_hud_hitmarker_3d_shotguns", 0) > 0 and self:GetStatL("Primary.NumShots") > 1
|
|
|
|
if hm3d or hm3d_sg then
|
|
net.Start("tfaHitmarker3D", true)
|
|
net.WriteVector(traceres.HitPos)
|
|
net.Send(ply)
|
|
else
|
|
net.Start("tfaHitmarker", true)
|
|
net.Send(ply)
|
|
end
|
|
end
|
|
|
|
SWEP.VMSeqCache = {}
|
|
local vm -- are you fucking kidding me
|
|
|
|
function SWEP:CheckVMSequence(seqname)
|
|
if not IsValid(self) then return false end
|
|
vm = self.OwnerViewModel
|
|
if not IsValid(vm) then return false end
|
|
local mdl = vm:GetModel()
|
|
if not mdl then return false end
|
|
self.VMSeqCache[mdl] = self.VMSeqCache[mdl] or {}
|
|
|
|
if self.VMSeqCache[mdl][seqname] == nil then
|
|
self.VMSeqCache[mdl][seqname] = vm:LookupSequence(seqname) >= 0
|
|
end
|
|
|
|
return self.VMSeqCache[mdl][seqname]
|
|
end
|
|
|
|
do
|
|
local function sorter(a, b)
|
|
return a.range < b.range
|
|
end
|
|
|
|
local function linear(a) return a end
|
|
|
|
function SWEP:BuildFalloffTable(input, step)
|
|
if step == nil then step = TFA.RangeFalloffLUTStep end
|
|
|
|
table.sort(input.lut, sorter)
|
|
|
|
if input.lut[1].range > 0 then
|
|
for i = #input.lut, 1, -1 do
|
|
input.lut[i + 1] = input.lut[i]
|
|
end
|
|
|
|
input.lut[1] = {range = 0, damage = 1}
|
|
end
|
|
|
|
local div = (input.units == "hammer" or input.units == "inches" or input.units == "inch" or input.units == "hu") and 1 or 0.0254
|
|
|
|
local build = {}
|
|
local minimal = input.lut[1].range
|
|
local maximal = input.lut[#input.lut].range
|
|
|
|
local fnrange = isfunction(input.range_func) and input.range_func or
|
|
input.range_func == "quintic" and TFA.Quintic or
|
|
input.range_func == "cubic" and TFA.Cubic or
|
|
input.range_func == "cosine" and TFA.Cosine or
|
|
input.range_func == "sinusine" and TFA.Sinusine or
|
|
linear
|
|
|
|
if input.bezier then
|
|
local build_range = {}
|
|
local build_damage = {}
|
|
|
|
for _, data in ipairs(input.lut) do
|
|
table.insert(build_range, data.range / div)
|
|
table.insert(build_damage, data.damage)
|
|
end
|
|
|
|
for i = 0, 1, step do
|
|
local value = fnrange(i)
|
|
table.insert(build, {TFA.tbezier(value, build_range), TFA.tbezier(value, build_damage)})
|
|
end
|
|
else
|
|
local current, next = input.lut[1], input.lut[2]
|
|
local nextindex = 1
|
|
|
|
for i = 0, 1, step do
|
|
local value = fnrange(i)
|
|
local interp = Lerp(value, minimal, maximal)
|
|
|
|
if next.range < interp then
|
|
nextindex = nextindex + 1
|
|
current, next = input.lut[nextindex], input.lut[nextindex + 1]
|
|
end
|
|
|
|
if not current or not next then break end -- safeguard
|
|
table.insert(build, {interp / div, Lerp(1 - (next.range - interp) / (next.range - current.range), current.damage, next.damage)})
|
|
end
|
|
end
|
|
|
|
return build
|
|
end
|
|
end
|
|
|
|
function SWEP:IncreaseRecoilLUT()
|
|
if not self:HasRecoilLUT() then return end
|
|
|
|
local self2 = self:GetTable()
|
|
local time = CurTime()
|
|
|
|
if not self:GetRecoilThink() then
|
|
self:SetRecoilThink(true)
|
|
end
|
|
|
|
if not self:GetRecoilLoop() then
|
|
local newvalue = self:GetRecoilInProgress() + self2.Primary_TFA.RecoilLUT["in"].increase
|
|
|
|
self:SetRecoilInProgress(math.min(1, newvalue))
|
|
|
|
self:SetRecoilInWait(time + self2.Primary_TFA.RecoilLUT["in"].wait)
|
|
|
|
if self:GetRecoilInProgress() >= 1 then
|
|
self:SetRecoilLoop(true)
|
|
self:SetRecoilLoopProgress(math.Clamp(newvalue % 1, 0, 1))
|
|
self:SetRecoilLoopWait(time + self2.Primary_TFA.RecoilLUT["loop"].wait)
|
|
end
|
|
|
|
return
|
|
end
|
|
|
|
local sub = 0
|
|
|
|
if self:GetRecoilOutProgress() ~= 0 then
|
|
local prev = self:GetRecoilOutProgress()
|
|
local newvalue = math.max(0, prev - self2.Primary_TFA.RecoilLUT["out"].increase)
|
|
self:SetRecoilOutProgress(newvalue)
|
|
self:SetRecoilLoopWait(time + self2.Primary_TFA.RecoilLUT["loop"].wait)
|
|
|
|
if newvalue ~= 0 then
|
|
return
|
|
end
|
|
|
|
sub = self2.Primary_TFA.RecoilLUT["out"].increase - prev
|
|
end
|
|
|
|
local newvalue = (self:GetRecoilLoopProgress() + self2.Primary_TFA.RecoilLUT["loop"].increase + sub) % 1
|
|
self:SetRecoilLoopProgress(newvalue)
|
|
self:SetRecoilLoopWait(time + self2.Primary_TFA.RecoilLUT["loop"].wait)
|
|
end
|
|
|
|
function SWEP:HasRecoilLUT()
|
|
return self.Primary_TFA.RecoilLUT ~= nil
|
|
end
|
|
|
|
do
|
|
local function linear(a) return a end
|
|
|
|
local function getfn(input)
|
|
return isfunction(input.func) and input.func or
|
|
input.func == "quintic" and TFA.Quintic or
|
|
input.func == "cubic" and TFA.Cubic or
|
|
input.func == "cosine" and TFA.Cosine or
|
|
input.func == "sinusine" and TFA.Sinusine or
|
|
linear
|
|
end
|
|
|
|
function SWEP:GetRecoilLUTAngle()
|
|
if not self:GetRecoilThink() then
|
|
return Angle()
|
|
end
|
|
|
|
local self2 = self:GetTable()
|
|
local isp = 1 - self:GetIronSightsProgress() * self2.GetStatL(self, "Primary.RecoilLUT_IronSightsMult")
|
|
|
|
if not self:GetRecoilLoop() then
|
|
-- currently, we only playing IN animation
|
|
|
|
local t = getfn(self2.Primary_TFA.RecoilLUT["in"])(self:GetRecoilInProgress())
|
|
|
|
local pitch = TFA.tbezier(t, self2.Primary_TFA.RecoilLUT["in"].points_p)
|
|
local yaw = TFA.tbezier(t, self2.Primary_TFA.RecoilLUT["in"].points_y)
|
|
|
|
return Angle(pitch * isp, yaw * isp)
|
|
end
|
|
|
|
local out = getfn(self2.Primary_TFA.RecoilLUT["out"])(self:GetRecoilOutProgress())
|
|
local loop = getfn(self2.Primary_TFA.RecoilLUT["loop"])(self:GetRecoilLoopProgress())
|
|
|
|
local pitch = TFA.tbezier(loop, self2.Primary_TFA.RecoilLUT["loop"].points_p)
|
|
local yaw = TFA.tbezier(loop, self2.Primary_TFA.RecoilLUT["loop"].points_y)
|
|
|
|
if out ~= 0 then
|
|
-- cooling out
|
|
self2.Primary_TFA.RecoilLUT["out"].points_p[1] = pitch
|
|
self2.Primary_TFA.RecoilLUT["out"].points_y[1] = yaw
|
|
|
|
local pitch2 = TFA.tbezier(out, self2.Primary_TFA.RecoilLUT["out"].points_p)
|
|
local yaw2 = TFA.tbezier(out, self2.Primary_TFA.RecoilLUT["out"].points_y)
|
|
|
|
return Angle(pitch2 * isp, yaw2 * isp)
|
|
end
|
|
|
|
return Angle(pitch * isp, yaw * isp)
|
|
end
|
|
end
|
|
|
|
local sv_tfa_recoil_legacy = GetConVar("sv_tfa_recoil_legacy")
|
|
|
|
function SWEP:GetAimVector()
|
|
return self:GetAimAngle():Forward()
|
|
end
|
|
|
|
function SWEP:GetAimAngle()
|
|
local ang = self:GetOwner():GetAimVector():Angle()
|
|
|
|
if sv_tfa_recoil_legacy:GetBool() and self:GetOwner():IsPlayer() then
|
|
ang:Add(self:GetOwner():GetViewPunchAngles())
|
|
elseif self:HasRecoilLUT() then
|
|
ang:Add(self:GetRecoilLUTAngle())
|
|
else
|
|
ang.p = ang.p + self:GetViewPunchP()
|
|
ang.y = ang.y + self:GetViewPunchY()
|
|
end
|
|
|
|
ang:Normalize()
|
|
return ang
|
|
end
|
|
|
|
function SWEP:EmitSoundNet(sound, ifp)
|
|
if ifp == nil then ifp = IsFirstTimePredicted() end
|
|
if not ifp then return end
|
|
|
|
if CLIENT and sp then return end
|
|
|
|
if CLIENT or sp then
|
|
self:EmitSound(sound)
|
|
return
|
|
end
|
|
|
|
local filter = RecipientFilter()
|
|
|
|
filter:AddPAS(self:GetPos())
|
|
|
|
if IsValid(self:GetOwner()) and self:GetOwner():IsPlayer() then
|
|
filter:RemovePlayer(self:GetOwner())
|
|
end
|
|
|
|
if filter:GetCount() == 0 then return end
|
|
|
|
net.Start("tfaSoundEvent", true)
|
|
net.WriteEntity(self)
|
|
net.WriteString(sound)
|
|
net.Send(filter)
|
|
end
|
|
|
|
function SWEP:StopSoundNet(sound, ifp)
|
|
if ifp == nil then ifp = IsFirstTimePredicted() end
|
|
if not ifp then return end
|
|
|
|
if CLIENT and sp then return end
|
|
|
|
if CLIENT or sp then
|
|
self:StopSound(sound)
|
|
return
|
|
end
|
|
|
|
local filter = RecipientFilter()
|
|
|
|
filter:AddPAS(self:GetPos())
|
|
|
|
if IsValid(self:GetOwner()) and self:GetOwner():IsPlayer() then
|
|
filter:RemovePlayer(self:GetOwner())
|
|
end
|
|
|
|
if filter:GetCount() == 0 then return end
|
|
|
|
net.Start("tfaSoundEventStop", true)
|
|
net.WriteEntity(self)
|
|
net.WriteString(sound)
|
|
net.Send(filter)
|
|
end
|