function SWEP:Attach(slot, att, silent, suppress)
    local slottbl = self.Attachments[slot]
    if slottbl.Installed then return end

    if !self:CanAttach(slot, att) then return end

    local atttbl = TacRP.GetAttTable(att)

    if !atttbl then return end
    if TacRP:PlayerGetAtts(self:GetOwner(), att) <= 0 then return end

    local inf_old = self:GetInfiniteAmmo()
    local ammo_old = self:GetAmmoType()

    slottbl.Installed = att

    TacRP:PlayerTakeAtt(self:GetOwner(), att, 1)

    if atttbl.OnAttach then
        atttbl.OnAttach(self)
    end

    if CLIENT then
        local attid = atttbl.ID

        net.Start("TacRP_attach")
        net.WriteEntity(self)
        net.WriteBool(true)
        net.WriteUInt(slot, 8)
        net.WriteUInt(attid, TacRP.Attachments_Bits)
        net.SendToServer()

        if game.SinglePlayer() then -- Due to bodygroups also being networked by engine, this will cause bodygroup "flickering"
            self:SetupModel(true)
            self:SetupModel(false)
        end

        if !silent then
            surface.PlaySound(slottbl.AttachSound or "")
        end
    elseif SERVER and !suppress then
        self:NetworkWeapon()
        TacRP:PlayerSendAttInv(self:GetOwner())
    end

    self:SetBurstCount(0)

    self:InvalidateCache()

    self:SetBaseSettings()

    if atttbl.CanToggle then
        self:SetTactical(true)
    end

    if self:GetFiremode() > self:GetFiremodeAmount() then
        self:SetFiremode(1)
    end

    local inf_new = self:GetInfiniteAmmo()
    local ammo_new = self:GetAmmoType()
    if SERVER then
        if inf_old and !inf_new then
            self:SetClip1(0)
        elseif (inf_new and !inf_old) or (ammo_old != ammo_new) then
            self:Unload(ammo_old)
        end
    end
end

function SWEP:Detach(slot, silent, suppress)
    local slottbl = self.Attachments[slot]
    if !slottbl.Installed then return end

    if !self:CanDetach(slot, slottbl.Installed) then return end

    TacRP:PlayerGiveAtt(self:GetOwner(), slottbl.Installed, 1)

    local inf_old = self:GetInfiniteAmmo()
    local ammo_old = self:GetAmmoType()

    local atttbl = TacRP.GetAttTable(slottbl.Installed)

    if atttbl and atttbl.OnDetach then
        atttbl.OnDetach(self)
    end

    slottbl.Installed = nil

    if CLIENT then
        net.Start("TacRP_attach")
        net.WriteEntity(self)
        net.WriteBool(false)
        net.WriteUInt(slot, 8)
        net.SendToServer()

        if game.SinglePlayer() then -- Due to bodygroups also being networked by engine, this will cause bodygroup "flickering"
            self:SetupModel(true)
            self:SetupModel(false)
        end

        if !silent then
            surface.PlaySound(slottbl.DetachSound or "")
        end
    elseif SERVER and !suppress then
        self:NetworkWeapon()
        TacRP:PlayerSendAttInv(self:GetOwner())
    end

    self:SetBurstCount(0)

    self:InvalidateCache()

    self:SetBaseSettings()

    if self:GetFiremode() > self:GetFiremodeAmount() then
        self:SetFiremode(1)
    end

    local nade = self:GetGrenade()
    if (nade.AdminOnly and self:GetOwner():GetAmmoCount(nade.Ammo) <= 0) or (nade.RequireStat and !self:GetValue(nade.RequireStat)) then
        self:SelectGrenade()
    end

    local inf_new = self:GetInfiniteAmmo()
    local ammo_new = self:GetAmmoType()
    if SERVER then
        if inf_old and !inf_new then
            self:SetClip1(0)
        elseif (inf_new and !inf_old) or (ammo_old != ammo_new) then
            self:Unload(ammo_old)
        end
    end
end

function SWEP:ToggleCustomize(on)
    if on == self:GetCustomize() or (on and self:GetValue("RunawayBurst") and self:GetBurstCount() > 0) then return end

    self:ScopeToggle(0)
    self:ToggleBlindFire(TacRP.BLINDFIRE_NONE)

    self:SetCustomize(on)

    self:SetShouldHoldType()
end

function SWEP:CanAttach(slot, att)
    local atttbl = TacRP.GetAttTable(att)

    local slottbl = self.Attachments[slot]

    if atttbl.Compatibility then
        local result = atttbl.Compatibility(self)

        if result == true then
            return true
        elseif result == false then
            return false
        end
    end

    local cat = slottbl.Category

    if !istable(cat) then
        cat = {cat}
    end

    local attcat = atttbl.Category

    if !istable(attcat) then
        attcat = {attcat}
    end

    if !TacRP.CanCustomize(self:GetOwner(), self, att, slot) then return false end

    for _, c in pairs(attcat) do
        if table.HasValue(cat, c) then
            return true
        end
    end

    return false
end

function SWEP:CanDetach(slot, att)
    local slottbl = self.Attachments[slot]

    if slottbl.Integral then return false end

    if !TacRP.CanCustomize(self:GetOwner(), self, att, slot, true) then return false end

    return true
end

function SWEP:ToggleTactical()
    local ret = self:RunHook("Hook_ToggleTactical")
    if !ret then
        self:EmitSound(self:GetValue("Sound_ToggleTactical"))
        self:SetTactical(!self:GetTactical())
    end
end
