function SWEP:GetRecoilResetTime(base)
    if base then
        return (self:GetBaseValue("RecoilResetInstant") and 0 or math.min(0.5, 60 / self:GetRPM(true))) + self:GetBaseValue("RecoilResetTime")
    else
        return (self:GetValue("RecoilResetInstant") and 0 or math.min(0.5, 60 / self:GetRPM())) + self:GetValue("RecoilResetTime")
    end
end

function SWEP:ThinkRecoil()
    if self:GetLastRecoilTime() + engine.TickInterval() + self:GetRecoilResetTime() < CurTime() then
        local rec = self:GetRecoilAmount()

        rec = rec - (FrameTime() * self:GetValue("RecoilDissipationRate"))
        rec = math.Clamp(rec, 0, self:GetValue("RecoilMaximum"))

        self:SetRecoilAmount(rec)
    end
end

function SWEP:ApplyRecoil()
    local rec = self:GetRecoilAmount()
    local rps = self:GetValue("RecoilPerShot")
    local cfm = self:GetCurrentFiremode()

    if rec == 0 then
        rps = rps * self:GetValue("RecoilFirstShotMult")
    end

    if self:GetOwner():Crouching() and self:GetOwner():OnGround() then
        rps = rps * self:GetValue("RecoilMultCrouch")
    end

    if cfm < 0 then
        rps = rps * self:GetValue("RecoilMultBurst")
    elseif cfm == 1 then
        rps = rps * self:GetValue("RecoilMultSemi")
    end

    if self:GetInBipod() then
        rps = rps * math.min(1, self:GetValue("BipodRecoil"))
    end

    rec = rec + rps

    rec = math.Clamp(rec, 0, self:GetValue("RecoilMaximum"))

    if self:UseRecoilPatterns() then
        self:SetRecoilDirection(self:GetRecoilPatternDirection(self:GetPatternCount()))
    else
        local stab = math.Clamp(self:GetValue("RecoilStability"), 0, 0.9)
        self:SetRecoilDirection(util.SharedRandom("tacrp_recoildir", -180 + stab * 90, -stab * 90))
    end

    -- self:SetRecoilDirection(-90)
    self:SetRecoilAmount(rec)
    self:SetLastRecoilTime(CurTime())

    local vis_kick = self:GetValue("RecoilVisualKick")
    local vis_shake = 0

    vis_kick = vis_kick * TacRP.ConVars["mult_recoil_vis"]:GetFloat()
    vis_shake = 0

    if self:GetInBipod() then
        vis_kick = vis_kick * math.min(1, self:GetValue("BipodKick"))
        vis_shake = math.max(0, 1 - self:GetValue("BipodKick"))
    end

    local vis_kick_v = vis_kick * 1
    local vis_kick_h = vis_kick * util.SharedRandom("tacrp_vis_kick_h", -1, 1)

    self:GetOwner():SetViewPunchAngles(Angle(vis_kick_v, vis_kick_h, vis_shake))

    -- self:GetOwner():SetFOV(self:GetOwner():GetFOV() * 0.99, 0)
    -- self:GetOwner():SetFOV(self:GetOwner():GetFOV(), 60 / (self:GetValue("RPM")))
end

function SWEP:RecoilDuration()
    -- return self:GetValue("RecoilResetTime")
    return 0.04 + math.Clamp(math.abs(self:GetValue("RecoilKick")) ^ 0.5, 0, 4) * 0.04
end

function SWEP:UseRecoilPatterns()
    if !TacRP.ConVars["recoilpattern"]:GetBool() then return false end
    if self:GetValue("ShootEnt") or self:GetValue("NoRecoilPattern") then return false end
    if self:GetValue("RPM") <= 100 then return false end
    if self:GetCurrentFiremode() < 0 then return false end

    return true
end

SWEP.RecoilPatternCache = {}
SWEP.RecoilPatternSeedCache = nil
function SWEP:GetRecoilPatternDirection(shot)
    local dir = 0

    if !self.RecoilPatternSeedCache then
        local cacheseed = self.RecoilPatternSeed or self:GetClass()
        if isstring(cacheseed) then
            local numseed = 0
            for _, i in ipairs(string.ToTable(cacheseed)) do
                numseed = numseed + string.byte(i)
            end
            numseed = numseed % 16777216
            cacheseed = numseed
        end
        self.RecoilPatternSeedCache = cacheseed
    end

    local seed = self.RecoilPatternSeedCache + shot

    if self.RecoilPatternCache[shot] then
        dir = self.RecoilPatternCache[shot]
    else
        self.RecoilPatternCache[1] = 0
        if self.RecoilPatternCache[shot - 1] then
            --dir = self.RecoilPatternCache[shot - 1]
            math.randomseed(seed)
            local stab = math.Clamp(self:GetValue("RecoilStability"), 0, 0.9)
            local max = self:GetBaseValue("RPM") / 60 * (1.1 + stab * 1.1)
            local cap = 120 --math.Clamp(30 + shot * (90 / max), 30, 120)
            --dir = dir + math.Rand(-stab * 90, stab * 90)
            dir = Lerp(0.4 + (shot / max) * 0.6, self.RecoilPatternCache[shot - 1], math.Rand(-(1 - stab) * cap, (1 - stab) * cap))
            if self:GetCurrentFiremode() != 1 then
                dir = Lerp(shot / max, dir, math.Clamp(dir * 1.667, -cap, cap))
            end
            math.randomseed(CurTime() + self:EntIndex())
            self.RecoilPatternCache[shot] = dir
            -- print(shot, cap, max, dir)
        else
            dir = 0
        end
    end

    return math.NormalizeAngle(dir - 90)
end
