--[[
| 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/
--]]

function SWEP:CanPrimaryAttack()
    local owner = self:GetOwner()

    -- Should we not fire? But first.
    if self:GetBuff_Hook("Hook_ShouldNotFireFirst") then return end

    -- We're holstering
    if IsValid(self:GetHolster_Entity()) then return end
    if self:GetHolster_Time() > 0 then return end

    -- Disabled (currently used only by deploy)
    if self:GetState() == ArcCW.STATE_DISABLE then return end

    -- Coostimzing
    if self:GetState() == ArcCW.STATE_CUSTOMIZE then
        if CLIENT and ArcCW.Inv_Hidden then
            ArcCW.Inv_Hidden = false
            gui.EnableScreenClicker(true)
        elseif game.SinglePlayer() then
            -- Kind of ugly hack: in SP this is only called serverside so we ask client to do the same check
            self:CallOnClient("CanPrimaryAttack")
        end
        return
    end

    -- A priority animation is playing (reloading, cycling, firemode etc)
    if self:GetPriorityAnim() then return end

    -- Inoperable, but internally (burst resetting for example)
    if self:GetWeaponOpDelay() > CurTime() then return end

    -- Safety's on, dipshit
    if self:GetCurrentFiremode().Mode == 0 then
        self:ChangeFiremode(false)
        self:SetNextPrimaryFire(CurTime())
        self.Primary.Automatic = false
        return
    end

    -- If we are an NPC, do our own little methods
    if owner:IsNPC() then self:NPC_Shoot() return end

    -- If we are in a UBGL, shoot the UBGL, not the gun
    if self:GetInUBGL() then self:ShootUBGL() return end

    -- Too early, come back later.
    if self:GetNextPrimaryFire() >= CurTime() then return end

    -- Gun is locked from heat.
    if self:GetHeatLocked() then return end

    -- Attempting a bash
    if self:GetState() != ArcCW.STATE_SIGHTS and owner:KeyDown(IN_USE) or self.PrimaryBash then self:Bash() return end

    -- Throwing weapon
    if self.Throwing then self:PreThrow() return end

    -- Too close to a wall
    if self:BarrelHitWall() > 0 then return end

    -- Can't shoot while sprinting
    if self:GetNWState() == ArcCW.STATE_SPRINT and !self:CanShootWhileSprint() then return end

    -- Maximum burst shots
    if (self:GetBurstCount() or 0) >= self:GetBurstLength() then return end

    -- We need to cycle
    if self:GetNeedCycle() then return end

    -- If we have a trigger delay, make sure its progress is done
    if self:GetBuff_Override("Override_TriggerDelay", self.TriggerDelay) and ((!self:GetBuff_Override("Override_TriggerCharge", self.TriggerCharge) and self:GetTriggerDelta() < 1)
            or (self:GetBuff_Override("Override_TriggerCharge", self.TriggerCharge) and self:IsTriggerHeld())) then
        return
    end

    -- Should we not fire?
    if self:GetBuff_Hook("Hook_ShouldNotFire") then return end

    -- We made it
    return true
end

function SWEP:TakePrimaryAmmo(num)
    if self:HasBottomlessClip() or self:Clip1() <= 0 then
        if self:Ammo1() <= 0 then return end
        if self:HasInfiniteAmmo() then return end
        self:GetOwner():RemoveAmmo(num, self:GetPrimaryAmmoType())
    return end
    self:SetClip1(self:Clip1() - num)
end

function SWEP:ApplyRandomSpread(dir, spread)
    local radius = math.Rand(0, 1)
    local theta = math.Rand(0, math.rad(360))
    local bulletang = dir:Angle()
    local forward, right, up = bulletang:Forward(), bulletang:Right(), bulletang:Up()
    local x = radius * math.sin(theta)
    local y = radius * math.cos(theta)

    dir:Set(dir + right * spread * x + up * spread * y)
end

function SWEP:PrimaryAttack()
    local owner = self:GetOwner()

    self.Primary.Automatic = true

    if !self:CanPrimaryAttack() then return end

    local clip = self:Clip1()
    local aps = self:GetBuff("AmmoPerShot")

    if self:HasBottomlessClip() then
        clip = self:Ammo1()
        if self:HasInfiniteAmmo() then
            clip = math.huge
        end
    end

    if clip < aps then
        self:SetBurstCount(0)
        self:DryFire()

        self.Primary.Automatic = false

        return
    end

    local dir = (owner:EyeAngles() + self:GetFreeAimOffset()):Forward() --owner:GetAimVector()
    local src = self:GetShootSrc()

    if bit.band(util.PointContents(src), CONTENTS_WATER) == CONTENTS_WATER and !(self.CanFireUnderwater or self:GetBuff_Override("Override_CanFireUnderwater")) then
        self:DryFire()
        return
    end

    if self:GetMalfunctionJam() then
        self:DryFire()
        return
    end

    -- Try malfunctioning
    local mal = self:DoMalfunction(false)
    if mal == true then
        local anim = "fire_jammed"
        self:PlayAnimation(anim, 1, true, 0, true)
        return
    end

    self:GetBuff_Hook("Hook_PreFireBullets")

    local desync = ArcCW.ConVars["desync"]:GetBool()
    local desyncnum = (desync and math.random()) or 0
    math.randomseed(math.Round(util.SharedRandom(self:GetBurstCount(), -1337, 1337, !game.SinglePlayer() and self:GetOwner():GetCurrentCommand():CommandNumber() or CurTime()) * (self:EntIndex() % 30241)) + desyncnum)

    self.Primary.Automatic = true

    local spread = ArcCW.MOAToAcc * self:GetBuff("AccuracyMOA")
    local disp = self:GetDispersion() * ArcCW.MOAToAcc / 10

    --dir:Rotate(Angle(0, ArcCW.StrafeTilt(self), 0))
    --dir = dir + VectorRand() * disp

    self:ApplyRandomSpread(dir, disp)

    if (CLIENT or game.SinglePlayer()) and ArcCW.ConVars["dev_shootinfo"]:GetInt() >= 3 and disp > 0 then
        local dev_tr = util.TraceLine({
            start = src,
            endpos = src + owner:GetAimVector() * 33000,
            mask = MASK_SHOT,
            filter = {self, self:GetOwner()}
        })
        local dist = (dev_tr.HitPos - src):Length()
        local r = dist / (1 / math.tan(disp)) -- had to google "trig cheat sheet to figure this one out"
        local a = owner:GetAimVector():Angle()
        local r_sqrt = r / math.sqrt(2)
        debugoverlay.Line(dev_tr.HitPos - a:Up() * r, dev_tr.HitPos + a:Up() * r, 5, color_white, true)
        debugoverlay.Line(dev_tr.HitPos - a:Right() * r, dev_tr.HitPos + a:Right() * r, 5, color_white, true)
        debugoverlay.Line(dev_tr.HitPos - a:Right() * r_sqrt - a:Up() * r_sqrt, dev_tr.HitPos + a:Right() * r_sqrt + a:Up() * r_sqrt, 5, color_white, true)
        debugoverlay.Line(dev_tr.HitPos - a:Right() * r_sqrt + a:Up() * r_sqrt, dev_tr.HitPos + a:Right() * r_sqrt - a:Up() * r_sqrt, 5, color_white, true)
        debugoverlay.Text(dev_tr.HitPos, math.Round(self:GetDispersion(), 1) .. "MOA (" .. math.Round(disp, 3) .. "°)", 5)
    end

    local delay = self:GetFiringDelay()

    local curtime = CurTime()
    local curatt = self:GetNextPrimaryFire()
    local diff = curtime - curatt

    if diff > engine.TickInterval() or diff < 0 then
        curatt = curtime
    end

    self:SetNextPrimaryFire(curatt + delay)
    self:SetNextPrimaryFireSlowdown(curatt + delay) -- shadow for ONLY fire time

    local num = self:GetBuff("Num")

    num = num + self:GetBuff_Add("Add_Num")

    local tracer = self:GetBuff_Override("Override_Tracer", self.Tracer)
    local tracernum = self:GetBuff_Override("Override_TracerNum", self.TracerNum)
    local lastout = self:GetBuff_Override("Override_TracerFinalMag", self.TracerFinalMag)
    if lastout >= clip then
        tracernum = 1
        tracer = self:GetBuff_Override("Override_TracerFinal", self.TracerFinal) or self:GetBuff_Override("Override_Tracer", self.Tracer)
    end
    local dmgtable = self.BodyDamageMults
    dmgtable = self:GetBuff_Override("Override_BodyDamageMults") or dmgtable

    -- drive by is cool
    src = ArcCW:GetVehicleFireTrace(self:GetOwner(), src, dir) or src

    local bullet      = {}
    bullet.Attacker   = owner
    bullet.Dir        = dir
    bullet.Src        = src
    bullet.Spread     = Vector(0, 0, 0) --Vector(spread, spread, spread)
    bullet.Damage     = 0
    bullet.Num        = num

    local sglove = math.ceil(num / 3)
    bullet.Force      = self:GetBuff("Force", true) or math.Clamp( ( (50 / sglove) / ( (self:GetBuff("Damage") + self:GetBuff("DamageMin")) / (self:GetBuff("Num") * 2) ) ) * sglove, 1, 3 )
                        -- Overperforming weapons get the jerf, underperforming gets boost
    bullet.Distance   = self:GetBuff("Distance", true) or 33300
    -- Setting AmmoType makes the engine look for the tracer effect on the ammo instead of TracerName!
    --bullet.AmmoType   = self.Primary.Ammo
    bullet.HullSize   = self:GetBuff("HullSize")
    bullet.Tracer     = tracernum or 0
    bullet.TracerName = tracer
    bullet.Weapon     = self
    bullet.Callback = function(att, tr, dmg)
        ArcCW:BulletCallback(att, tr, dmg, self)
    end

    local shootent = self:GetBuff("ShootEntity", true) --self:GetBuff_Override("Override_ShootEntity", self.ShootEntity)
    local shpatt   = self:GetBuff_Override("Override_ShotgunSpreadPattern", self.ShotgunSpreadPattern)
    local shpattov = self:GetBuff_Override("Override_ShotgunSpreadPatternOverrun", self.ShotgunSpreadPatternOverrun)

    local extraspread = AngleRand() * self:GetDispersion() * ArcCW.MOAToAcc / 10

    local projectiledata = {}

    if shpatt or shpattov or shootent then
        if shootent then
            projectiledata.ent = shootent
            projectiledata.vel = self:GetBuff("MuzzleVelocity")
        end

        bullet = self:GetBuff_Hook("Hook_FireBullets", bullet)

        if !bullet then return end

        local doent = shootent and num or bullet.Num
        local minnum = shootent and 1 or 0

        if doent > minnum then
            for n = 1, bullet.Num do
                bullet.Num = 1

                local dispers = self:GetBuff_Override("Override_ShotgunSpreadDispersion", self.ShotgunSpreadDispersion)
                local offset  = self:GetShotgunSpreadOffset(n)
                local calcoff = dispers and (offset * self:GetDispersion() * ArcCW.MOAToAcc / 10) or offset

                local ang = owner:EyeAngles() + self:GetFreeAimOffset()
                local ang2 = Angle(ang)
                ang2:RotateAroundAxis(ang:Right(), -1 * calcoff.p)
                ang2:RotateAroundAxis(ang:Up(), calcoff.y)
                ang2:RotateAroundAxis(ang:Forward(), calcoff.r)

                if !self:GetBuff_Override("Override_NoRandSpread", self.NoRandSpread) then -- Needs testing
                    ang2 = ang2 + AngleRand() * spread / 5
                end

                if shootent then
                    projectiledata.ang = ang2

                    self:DoPrimaryFire(true, projectiledata)
                else
                    bullet.Dir = ang2:Forward()

                    self:DoPrimaryFire(false, bullet)
                end
            end
        elseif shootent then
            local ang = owner:EyeAngles() + self:GetFreeAimOffset()

            if !self:GetBuff_Override("Override_NoRandSpread", self.NoRandSpread) then
               -- ang = (dir + VectorRand() * spread / 5):Angle()

                local newdir = Vector(dir)
                self:ApplyRandomSpread(newdir, spread / 5)
                ang = newdir:Angle()
            end

            projectiledata.ang = ang

            self:DoPrimaryFire(true, projectiledata)
        end
    else
        if !bullet then return end

        for n = 1, bullet.Num do
            bullet.Num = 1
            local dirry = Vector(dir.x, dir.y, dir.z)
            math.randomseed(math.Round(util.SharedRandom(n, -1337, 1337, !game.SinglePlayer() and self:GetOwner():GetCurrentCommand():CommandNumber() or CurTime()) * (self:EntIndex() % 30241)) + desyncnum)
            if !self:GetBuff_Override("Override_NoRandSpread", self.NoRandSpread) then
                self:ApplyRandomSpread(dirry, spread)
                bullet.Dir = dirry
            end
            bullet = self:GetBuff_Hook("Hook_FireBullets", bullet) or bullet

            self:DoPrimaryFire(false, bullet)
        end
    end

    self:DoRecoil()

    self:SetNthShot(self:GetNthShot() + 1)

    owner:DoAnimationEvent(self:GetBuff_Override("Override_AnimShoot") or self.AnimShoot)

    local shouldsupp = SERVER and !game.SinglePlayer()

    if shouldsupp then SuppressHostEvents(owner) end

    self:DoEffects()

    self:SetBurstCount(self:GetBurstCount() + 1)

    self:TakePrimaryAmmo(aps)

    self:DoShootSound()
    self:DoPrimaryAnim()

    if self:GetCurrentFiremode().Mode < 0 and self:GetBurstCount() == self:GetBurstLength() then
        local postburst = (self:GetCurrentFiremode().PostBurstDelay or 0)
        self:SetWeaponOpDelay(CurTime() + postburst * self:GetBuff_Mult("Mult_PostBurstDelay") + self:GetBuff_Add("Add_PostBurstDelay"))
    end

    if (self:GetIsManualAction()) and !(self.NoLastCycle and self:Clip1() == 0) then
        local fireanim = self:GetBuff_Hook("Hook_SelectFireAnimation") or self:SelectAnimation("fire")
        local firedelay = self.Animations[fireanim].MinProgress or 0
        self:SetNeedCycle(true)
        self:SetWeaponOpDelay(CurTime() + (firedelay * self:GetBuff_Mult("Mult_CycleTime")))
        self:SetNextPrimaryFire(CurTime() + 0.1)
    end

    self:ApplyAttachmentShootDamage()

    self:AddHeat(self:GetBuff("HeatGain"))

    mal = self:DoMalfunction(true)
    if mal == true then
        local anim = "fire_jammed"
        self:PlayAnimation(anim, 1, true, 0, true)
    end

    if self:GetCurrentFiremode().Mode == 1 then
        self.LastTriggerTime = -1 -- Cannot fire again until trigger released
        self.LastTriggerDuration = 0
    end

    self:GetBuff_Hook("Hook_PostFireBullets")

    if shouldsupp then SuppressHostEvents(nil) end
end

function SWEP:TryBustDoor(ent, dmg)
    ArcCW.TryBustDoor(ent, dmg)
end

function SWEP:DoShootSound(sndoverride, dsndoverride, voloverride, pitchoverride)
    local fsound = self.ShootSound
    local suppressed = self:GetBuff_Override("Silencer")

    if suppressed then
        fsound = self.ShootSoundSilenced
    end

    local firstsound = self.FirstShootSound

    if self:GetBurstCount() == 1 and firstsound then
        fsound = firstsound

        local firstsil = self.FirstShootSoundSilenced

        if suppressed then
            fsound = firstsil and firstsil or self.ShootSoundSilenced
        end
    end

    local lastsound = self.LastShootSound

    local clip = self:Clip1()

    if clip == 1 and lastsound then
        fsound = lastsound

        local lastsil = self.LastShootSoundSilenced

        if suppressed then
            fsound = lastsil and lastsil or self.ShootSoundSilenced
        end
    end

    fsound = self:GetBuff_Hook("Hook_GetShootSound", fsound)

    local distancesound = self.DistantShootSound

    if suppressed then
        distancesound = self.DistantShootSoundSilenced
    end

    distancesound = self:GetBuff_Hook("Hook_GetDistantShootSound", distancesound)

    local spv = self.ShootPitchVariation
    local volume = self.ShootVol
    local pitch  = self.ShootPitch * math.Rand(1 - spv, 1 + spv) * self:GetBuff_Mult("Mult_ShootPitch")

    local v = ArcCW.ConVars["weakensounds"]:GetFloat()

    volume = volume - v

    volume = volume * self:GetBuff_Mult("Mult_ShootVol")

    volume = math.Clamp(volume, 51, 149)
    pitch  = math.Clamp(pitch, 0, 255)

    if    sndoverride        then    fsound    = sndoverride end
    if    dsndoverride    then    distancesound = dsndoverride end
    if    voloverride        then    volume    = voloverride end
    if    pitchoverride    then    pitch    = pitchoverride end

    if distancesound then self:MyEmitSound(distancesound, 149, pitch, 0.5, CHAN_WEAPON + 1) end

    if fsound then self:MyEmitSound(fsound, volume, pitch, 1, CHAN_WEAPON) end

    local data = {
        sound   = fsound,
        volume  = volume,
        pitch   = pitch,
    }

    self:GetBuff_Hook("Hook_AddShootSound", data)
end

function SWEP:GetMuzzleVelocity()
    local vel = self:GetBuff_Override("Override_PhysBulletMuzzleVelocity", self.PhysBulletMuzzleVelocity)

    if !vel then
        vel = self:GetBuff("Range") * 3.5

        if self:GetBuff("DamageMin") > self:GetBuff("Damage") then
            vel = vel * 2
        end
        vel = math.Clamp(vel, 200, 1000)
    end

    vel = vel / ArcCW.HUToM

    vel = vel * self:GetBuff_Mult("Mult_PhysBulletMuzzleVelocity")

    vel = vel * ArcCW.ConVars["bullet_velocity"]:GetFloat()

    return vel
end

function SWEP:DoPrimaryFire(isent, data)
    local clip = self:Clip1()
    if self:HasBottomlessClip() then
        if !self:GetOwner():IsPlayer() then
            clip = math.huge
        else
            clip = self:Ammo1()
        end
    end
    local owner = self:GetOwner()

    local shouldphysical = ArcCW.ConVars["bullet_enable"]:GetBool()

    if self.AlwaysPhysBullet or self:GetBuff_Override("Override_AlwaysPhysBullet") then
        shouldphysical = true
    end

    if self.NeverPhysBullet or self:GetBuff_Override("Override_NeverPhysBullet") then
        shouldphysical = false
    end

    if isent then
        self:FireRocket(data.ent, data.vel, data.ang, self.PhysBulletDontInheritPlayerVelocity)
    else
        -- if !game.SinglePlayer() and !IsFirstTimePredicted() then return end
        if !IsFirstTimePredicted() then return end

        if shouldphysical then
            local tracernum = data.Tracer or 1
            local phystracer = self:GetBuff_Override("Override_PhysTracerProfile", self.PhysTracerProfile)
            local lastout = self:GetBuff_Override("Override_TracerFinalMag", self.TracerFinalMag)
            if lastout >= self:Clip1() then
                phystracer = self:GetBuff_Override("Override_PhysTracerProfileFinal", self.PhysTracerProfileFinal) or phystracer
            elseif tracernum == 0 or clip % tracernum != 0 then
                phystracer = 7
            end

            local vel = self:GetMuzzleVelocity()

            vel = vel * data.Dir:GetNormalized()

            ArcCW:ShootPhysBullet(self, data.Src, vel, phystracer or 0)
        else
            owner:FireBullets(data, true)
        end
    end
end

function SWEP:DoPrimaryAnim()
    local anim = "fire"

    local inbipod = self:InBipod()
    local iron    = self:GetState() == ArcCW.STATE_SIGHTS

    -- Needs testing
    if inbipod then
        anim = self:SelectAnimation("fire_bipod") or self:SelectAnimation("fire") or anim
    else
        anim = self:SelectAnimation("fire") or anim
    end

    if (self.ProceduralIronFire and iron) or (self.ProceduralRegularFire and !iron) then anim = nil end

    anim = self:GetBuff_Hook("Hook_SelectFireAnimation", anim) or anim

    local time = self:GetBuff_Mult("Mult_FireAnimTime", anim) or 1

    if anim then self:PlayAnimation(anim, time, true, 0, false) end
end

function SWEP:DoPenetration(tr, penleft, alreadypenned)
    local bullet = {
        Damage = self:GetDamage((tr.HitPos - tr.StartPos):Length() * ArcCW.HUToM),
        DamageType = self:GetBuff_Override("Override_DamageType") or self.DamageType,
        Weapon = self,
        Penetration = self:GetBuff("Penetration"),
        Attacker = self:GetOwner(),
        Travelled = (tr.HitPos - tr.StartPos):Length()
    }

    ArcCW:DoPenetration(tr, bullet.Damage, bullet, penleft, false, alreadypenned)
end

function SWEP:GetFiringDelay()
    local delay = (self.Delay * (1 / self:GetBuff_Mult("Mult_RPM")))
    delay = self:GetBuff_Hook("Hook_ModifyRPM", delay) or delay

    return delay
end

function SWEP:GetShootSrc()
    local owner = self:GetOwner()

    if !IsValid(owner) then return self:GetPos() end
    if owner:IsNPC() then return owner:GetShootPos() end

    local dir    = owner:EyeAngles()
    local offset = Vector(0, 0, 0)

    if self:GetOwner():Crouching() then
        offset = self:GetBuff_Override("Override_BarrelOffsetCrouch") or self.BarrelOffsetCrouch or offset
    end

    if self:GetNWState() == ArcCW.STATE_SIGHTS then
        offset = LerpVector(self:GetNWSightDelta(), offset, self:GetBuff_Override("Override_BarrelOffsetSighted", self.BarrelOffsetSighted) or offset)
    else
        offset = LerpVector(1 - self:GetNWSightDelta(), offset, self:GetBuff_Override("Override_BarrelOffsetHip", self.BarrelOffsetHip) or offset)
    end

    local src = owner:EyePos()


    src = src + dir:Right()   * offset[1]
    src = src + dir:Forward() * offset[2]
    src = src + dir:Up()      * offset[3]

    return src
end

function SWEP:GetShotgunSpreadOffset(num)
    local rotate = Angle()
    local spreadpt = self:GetBuff_Override("Override_ShotgunSpreadPattern") or self.ShotgunSpreadPattern or {}
    local spreadov = self:GetBuff_Override("Override_ShotgunSpreadPatternOverrun") or self.ShotgunSpreadPatternOverrun or { Angle() }

    if istable(spreadpt) and istable(spreadov) then
        spreadpt["BaseClass"] = nil
        spreadov["BaseClass"] = nil

        if num > #spreadpt then
            if spo then
                num = num - #spreadpt
                num = math.fmod(num, #spreadov) + 1
                rotate = spreadov[num]
            else
                num = math.fmod(num, #spreadpt) + 1
                rotate = spreadpt[num]
            end
        else
            rotate = spreadpt[num]
        end
    end

    local rottoang = {}
    rottoang.num = num
    rottoang.ang = rotate

    rotate = self:GetBuff_Hook("Hook_ShotgunSpreadOffset", rottoang).ang

    return rotate or Angle()
end

function SWEP:GetDispersion()
    local owner = self:GetOwner()

    if vrmod and vrmod.IsPlayerInVR(owner) then return 0 end

    local hipdisp = self:GetBuff("HipDispersion")
    local sights  = self:GetState() == ArcCW.STATE_SIGHTS

    local hip = hipdisp

    local sightdisp = self:GetBuff("SightsDispersion")
    if sights then hip = Lerp(self:GetNWSightDelta(), sightdisp, hipdisp) end

    local speed = owner:GetAbsVelocity():Length()
    local maxspeed = owner:GetWalkSpeed() * self:GetBuff("SpeedMult")
    if sights then maxspeed = maxspeed * self:GetBuff("SightedSpeedMult") end
    speed = math.Clamp(speed / maxspeed, 0, 2)

    if owner:OnGround() or owner:WaterLevel() > 0 and owner:GetMoveType() != MOVETYPE_NOCLIP then
        hip = hip + speed * self:GetBuff("MoveDispersion")
    elseif owner:GetMoveType() != MOVETYPE_NOCLIP then
        hip = hip + math.max(speed * self:GetBuff("MoveDispersion"), self:GetBuff("JumpDispersion"))
    end

    if self:InBipod() then hip = hip * (self.BipodDispersion * self:GetBuff_Mult("Mult_BipodDispersion")) end

    if ArcCW.ConVars["mult_crouchdisp"]:GetFloat() != 1 and owner:OnGround() and owner:Crouching() then
        hip = hip * ArcCW.ConVars["mult_crouchdisp"]:GetFloat()
    end

    if ArcCW.ConVars["freeaim"]:GetInt() == 1 and !sights then
        hip = hip ^ 0.9
    end

    --local t = hook.Run("ArcCW_ModDispersion", self, {dispersion = hip})
    --hip = t and t.dispersion or hip
    hip = self:GetBuff_Hook("Hook_ModDispersion", hip) or hip

    return hip
end

function SWEP:DoShellEject(atti)
    local eff = self:GetBuff_Override("Override_ShellEffect") or self.ShellEffect or "arccw_shelleffect"

    if eff == "NONE" then return end

    local owner = self:GetOwner()

    if !IsValid(owner) then return end

    local vm = self

    if !owner:IsNPC() then owner:GetViewModel() end

    local att = vm:GetAttachment(atti or self:GetBuff_Override("Override_CaseEffectAttachment") or self.CaseEffectAttachment or 2)

    if !att then return end

    local pos, ang = att.Pos, att.Ang

    if pos and ang and self.ShellEjectPosCorrection then
        local up = ang:Up()
        local right = ang:Right()
        local forward = ang:Forward()
        pos = pos + up * self.ShellEjectPosCorrection.z + right * self.ShellEjectPosCorrection.x + forward * self.ShellEjectPosCorrection.y
    end

    local ed = EffectData()
    ed:SetOrigin(pos)
    ed:SetAngles(ang)
    ed:SetAttachment(atti or self:GetBuff_Override("Override_CaseEffectAttachment") or self.CaseEffectAttachment or 2)
    ed:SetScale(1)
    ed:SetEntity(self)
    ed:SetNormal(ang:Forward())
    ed:SetMagnitude(100)

    local efov = {}
    efov.eff = eff
    efov.fx  = ed

    if self:GetBuff_Hook("Hook_PreDoEffects", efov) == true then return end

    util.Effect(eff, ed)
end

function SWEP:DoEffects(att)
    if !game.SinglePlayer() and !IsFirstTimePredicted() then return end

    local ed = EffectData()
    ed:SetStart(self:GetShootSrc())
    ed:SetOrigin(self:GetShootSrc())
    ed:SetScale(1)
    ed:SetEntity(self)
    ed:SetAttachment(att or self:GetBuff_Override("Override_MuzzleEffectAttachment") or self.MuzzleEffectAttachment or 1)

    local efov = {}
    efov.eff = "arccw_muzzleeffect"
    efov.fx  = ed

    if self:GetBuff_Hook("Hook_PreDoEffects", efov) == true then return end

    util.Effect("arccw_muzzleeffect", ed)
end

function SWEP:DryFire()

    if self.Animations.fire_dry then
        return self:PlayAnimation("fire_dry", 1, true, 0, true)
    end
    self:MyEmitSound(self.ShootDrySound or "weapons/arccw/dryfire.wav", 75, 100, 1, CHAN_ITEM)
    self:SetNextPrimaryFire(CurTime() + 0.25)
end

function SWEP:DoRecoil()
    local single = game.SinglePlayer()

    if !single and !IsFirstTimePredicted() then return end

    if single and self:GetOwner():IsValid() and SERVER then self:CallOnClient("DoRecoil") end

    -- math.randomseed(self:GetBurstLength() + (self.Recoil * 409) + (self.RecoilSide * 519))

    local rec = {
        Recoil = 1,
        RecoilSide = 1,
        VisualRecoilMul = 1
    }
    rec = self:GetBuff_Hook("Hook_ModifyRecoil", rec) or rec

    local recoil = rec.Recoil
    local side   = rec.RecoilSide
    local visual = rec.VisualRecoilMul

    local rmul = (recoil or 1) * self:GetBuff_Mult("Mult_Recoil")
    local recv = (visual or 1) * self:GetBuff_Mult("Mult_VisualRecoilMult")
    local recs = (side or 1)   * self:GetBuff_Mult("Mult_RecoilSide")

    -- local rrange = math.Rand(-recs, recs) * self.RecoilSide

    -- local irec = math.Rand(rrange - 1, rrange + 1)
    -- local recu = math.Rand(0.5, 1)

    local irec = math.Rand(-1, 1)
    local recu = 1

    if self:InBipod() then
        local b = self.BipodRecoil * self:GetBuff_Mult("Mult_BipodRecoil")

        rmul = rmul * b
        recs = recs * b
        recv = recv * b
    end

    local recoiltbl = self:GetBuff_Override("Override_ShotRecoilTable") or self.ShotRecoilTable

    if recoiltbl and recoiltbl[self:GetBurstCount()] then rmul = rmul * recoiltbl[self:GetBurstCount()] end

    if ArcCW.ConVars["mult_crouchrecoil"]:GetFloat() != 1 and self:GetOwner():OnGround() and self:GetOwner():Crouching() then
        rmul = rmul * ArcCW.ConVars["mult_crouchrecoil"]:GetFloat()
    end

    local punch = Angle()

    punch = punch + (self:GetBuff_Override("Override_RecoilDirection", self.RecoilDirection) * math.max(self.Recoil, 0.25) * recu * recv * rmul)
    punch = punch + (self:GetBuff_Override("Override_RecoilDirectionSide", self.RecoilDirectionSide) * math.max(self.RecoilSide, 0.25) * irec * recv * rmul)
    punch = punch + Angle(0, 0, 90) * math.Rand(-1, 1) * math.Clamp(self.Recoil, 0.25, 1) * recv * rmul * 0.01
    punch = punch * (self.RecoilPunch or 1) * self:GetBuff_Mult("Mult_RecoilPunch")

    self:SetFreeAimAngle(self:GetFreeAimAngle() - punch)

    if CLIENT then self:OurViewPunch(punch) end

    if CLIENT or single then
        recv = recv * self.VisualRecoilMult

        self.RecoilAmount     = self.RecoilAmount + (self.Recoil * rmul * recu)
        self.RecoilAmountSide = self.RecoilAmountSide + (self.RecoilSide * irec * recs * rmul)
        self.RecoilPunchBack  = math.Clamp(self.RecoilAmount * recv * 5, 1, 5)

        if self.MaxRecoilBlowback > 0 then
            self.RecoilPunchBack = math.Clamp(self.RecoilPunchBack, 0, self.MaxRecoilBlowback)
        end

        self.RecoilPunchSide = self.RecoilSide * 0.1 * irec * recv * rmul
        self.RecoilPunchUp   = self.RecoilRise * 0.1 * recu
    end

    -- math.randomseed(CurTime() + (self:EntIndex() * 3))
end

function SWEP:GetBurstLength()
    local clip = self:Clip1()
    if self:HasBottomlessClip() then
        clip = self:Ammo1()
        if self:HasInfiniteAmmo() then
            clip = math.huge
        end
    end
    --if clip == 0 then return 1 end

    local len = self:GetCurrentFiremode().Mode

    if !len then return self:GetBurstCount() + 10 end

    local hookedlen = self:GetBuff_Hook("Hook_GetBurstLength", len)

    if len == 1 then return 1 end
    if len >= 2 then return self:GetBurstCount() + 10 end

    if hookedlen != len then return hookedlen end

    if len < 0 then return -len end

    return self:GetBurstCount() + 10
end

function SWEP:FireAnimationEvent(pos, ang, event, options)
    return true
end

function SWEP:IsRampupWeapon()
    local ovr = self:GetBuff_Override("Override_IsRampupWeapon")
    if ovr != nil then return ovr end
    return self:GetBuff("Damage") < self:GetBuff("DamageMin")
end

function SWEP:GetMinMaxRange()
    local decrease = !self:IsRampupWeapon()

    local min = self:GetBuff_Override("Override_RangeMin", self.RangeMin or 0)
    local max = self:GetBuff_Override("Override_Range", self.Range)
    local min_add = self:GetBuff_Add("Add_RangeMin")
    local max_add = self:GetBuff_Add("Add_Range")
    local min_mult = self:GetBuff_Mult("Mult_RangeMin")
    local max_mult = self:GetBuff_Mult("Mult_Range")

    if decrease then
        -- MinRange is also affected by Mult_Range, this is intentional
        local total_min = math.max((min + min_add) * min_mult * max_mult, 0)
        return total_min, math.max((max + max_add) * max_mult, total_min)
    else
        -- For "rampup weapons" (dmgmin > dmg), range buffs *decrease* range, as it ramps up damage quicker
        -- After all, +Range is supposed to be a positive buff no matter the kind of gun
        local total_min = math.max((min - min_add) / min_mult / max_mult, 0)
        return total_min, math.max((max - max_add) / max_mult, total_min)
    end
end

function SWEP:GetRangeFraction(range)
    local min, max = self:GetMinMaxRange()
    if range < min then
        return 0
    else
        return math.Clamp((range - min) / (max - min), 0, 1)
    end
end

function SWEP:GetDamage(range, pellet)
    local ovr = self:GetBuff_Override("Override_Num")
    local add = self:GetBuff_Add("Add_Num")
    local mul = self:GetBuff_Mult("Mult_Num")

    local num = self.Num
    local nbr = (ovr or num) * mul + add
    local factor = 1

    -- Total damage should be unchanged regardless of whether the weapon originally fired 1 pellet or > 1
    -- If pellet is set, we return per-pellet damage instead of total damage
    if pellet and num == 1 then
        factor = 1 / ((ovr or 1) * mul + add)
    elseif num != nbr then
        factor = num / nbr
    end

    --factor = ((pellet and num == 1) and (1 / ((ovr or 1) + add))) or ((num != nbr) and (num / nbr)) or 1

    if !pellet then factor = factor * nbr end

    local dmgmax = self:GetBuff("Damage") * factor
    local dmgmin = self:GetBuff("DamageMin") * factor
    local delta = self:GetRangeFraction(range)

    local lerped = Lerp(delta, dmgmax, dmgmin)

    return lerped
end

function SWEP:SecondaryAttack()
    return self.Melee2 and self:Bash(true)
end

function SWEP:CanShootWhileSprint()
    return ArcCW.ConVars["mult_shootwhilesprinting"]:GetBool() or self:GetBuff_Override("Override_ShootWhileSprint", self.ShootWhileSprint)
end
