```lua
-- shared.lua
SWEP.Weight = 5
SWEP.DrawAmmo = true
SWEP.DrawCrosshair = false
SWEP.CSMuzzleFlashes = true
SWEP.Base = "weapon_base"
SWEP.IsQWB = true
SWEP.Author = "qurs"
SWEP.Contact = ""
SWEP.Purpose = ""
SWEP.Instructions = ""
SWEP.ViewModel = "models/weapons/v_knife_t.mdl"
SWEP.DefaultScopeMat = Material("materials/qwb/scope/awp.png")
SWEP.Category = "qurs' weapons base"
SWEP.Spawnable = false
SWEP.UseHands = true
SWEP.Secondary.ClipSize = -1
SWEP.Secondary.DefaultClip = 0
SWEP.Secondary.Automatic = false
SWEP.Secondary.Ammo = "none"
SWEP.ShellType = "9mm"
SWEP.SafetyStanceSound = Sound("weapons/smg1/switch_single.wav")
local angle_zero = Angle()
local passiveHoldTypes = {
	revolver = "normal",
	ar2 = "passive",
	smg = "passive",
}

-- Позиции и углы для разных типов оружия
local defaultBulletPosAng = {
	default = {Vector(7.7, 0.4, 3.95), Angle(-3, 5.5, 0)},
	revolver = {Vector(7.7, 0.4, 3.95), Angle(-3, 5.5, 0)},
	ar2 = {Vector(20, -0.8, 11.2), Angle(-9.5, 0, 0)},
	smg = {Vector(14, -0.8, 6.8), Angle(-9.5, 0, 0)},
}

-- Вычисление пассивного типа захвата
function SWEP:CalculatePassiveHoldType()
	return passiveHoldTypes[self.HoldType] or "normal"
end

-- Обработка изменения режима безопасности
function SWEP:SafetyStanceChanged(old, new)
	local owner = self:GetOwner()
	if not IsValid(owner) then return end
	owner:EmitSound(self.SafetyStanceSound, nil, nil, self.AimSoundVolume or 0.25)
	if new then
		self:SetHoldType(self.PassiveHoldType or self:CalculatePassiveHoldType())
	else
		self:SetHoldType(self.HoldType)
	end
end

-- Настройка сетевых переменных
function SWEP:SetupDataTables()
	self:NetworkVar("Bool", 0, "SafetyStance")
	self:NetworkVar("Vector", 0, "LocalMuzzlePos")
	self:NetworkVar("Angle", 0, "LocalMuzzleAng")
	if SERVER then self:NetworkVarNotify("SafetyStance", self.SafetyStanceChanged) end
end

-- Инициализация оружия
function SWEP:Initialize()
	self:SetHoldType(self.HoldType)
	local holdType = self:GetHoldType()
	local bulletData = defaultBulletPosAng[holdType] or defaultBulletPosAng.default
	local lpos, lang = unpack(bulletData)
	self._sourceLocalMuzzlePos = self.ShootPos or lpos
	self._sourceLocalMuzzleAng = self.ShootAng or lang
	if SERVER then
		self:SetLocalMuzzlePos(self._sourceLocalMuzzlePos)
		self:SetLocalMuzzleAng(self._sourceLocalMuzzleAng)
	end
end

function SWEP:Deploy()
	return true
end

function SWEP:Holster()
	if SERVER then self.Ironsighted = false end
	return true
end

function SWEP:OnRemove()
	self:Holster()
end

-- Перезарядка оружия
function SWEP:Reload()
	if not IsFirstTimePredicted() then return end
	local owner = self:GetOwner()
	if not IsValid(owner) then return end
	if owner:KeyDown(IN_USE) then return end
	if qwb.isPlayerRunning(owner) then return end
	if not self:DefaultReload(ACT_VM_RELOAD) then return end
	owner:SetAnimation(PLAYER_RELOAD)
	if SERVER then
		self.Ironsighted = false
		net.Start("qwb.setIronsight")
		net.WriteBool(self.Ironsighted)
		net.Send(owner)
	end

	self:SetNextPrimaryFire(CurTime() + 2)
end

-- Получение позиции ствола
function SWEP:GetMuzzlePos()
	local owner = self:GetOwner()
	if not IsValid(owner) then return end
	local attID = owner:LookupAttachment("anim_attachment_rh")
	if attID == 0 then return end
	local att = owner:GetAttachment(attID)
	if not att then return end
	local lpos, lang = self:GetLocalMuzzlePos(), self:GetLocalMuzzleAng()
	local pos, ang = LocalToWorld(lpos, lang, att.Pos, att.Ang)
	return pos, ang
end

function SWEP:GetBoneAng()
	local ang = self:GetLocalMuzzleAng()
	local sourceAng = self._sourceLocalMuzzleAng
	local pitch = ang.p - sourceAng.p
	return Angle(-pitch, 0, 0)
end

function SWEP:GetBulletSourcePos()
	return self:GetMuzzlePos()
end

-- Стрельба пулями
function SWEP:ShootBullet(src, dir, num_bullets, aimcone, tracer)
	local bullet = {
		Num = num_bullets,
		Src = src,
		Dir = dir,
		Spread = Vector(aimcone, aimcone, 0),
		Tracer = tracer or 5,
		Force = 1,
		Damage = self.Primary.Damage,
		AmmoType = self.Primary.Ammo,
		TracerName = self.TracerName,
	}

	self:GetOwner():FireBullets(bullet)
	self:TakePrimaryAmmo(1)
	self:ShootEffects()
end

-- Кастомизация вспышки ствола
function SWEP:MuzzleFlashCustom()
	if SERVER then
		net.Start("qwb.muzzleFlashLight")
		net.WriteEntity(self)
		net.SendPVS(self:GetBulletSourcePos())
		return
	end

	local effectData = EffectData()
	effectData:SetEntity(self)
	effectData:SetFlags(1)
	util.Effect("MuzzleFlash", effectData)
end

-- Эффект выброса гильз
function SWEP:ShellEffect()
	if SERVER then return end
	if self.NoShell then return end
	local shellType = self.ShellType
	local effectData = EffectData()
	local pos = self:GetPos()
	if self.ShellOffset then
		local att = self:GetAttachment(self:LookupAttachment("muzzle"))
		if att then pos = LocalToWorld(self.ShellOffset, angle_zero, att.Pos, att.Ang) end
	end

	effectData:SetOrigin(pos)
	effectData:SetFlags(self.ShellVelocity or 75)
	util.Effect("EjectBrass_" .. shellType, effectData)
end

-- Обработка эффектов стрельбы
function SWEP:ShootEffects()
	self:SendWeaponAnim(ACT_VM_PRIMARYATTACK)
	self:MuzzleFlashCustom()
	self:ShellEffect()
	self:EmitSound(self.Primary.Sound)
	self.RecoilAnimBack = nil
	self.RecoilAnim = true
end

-- Основная атака (стрельба)
function SWEP:PrimaryAttack()
	if not self:CanPrimaryAttack() then return end
	if not IsValid(self) or not IsValid(self:GetOwner()) then return end
	local owner = self:GetOwner()
	local pos, ang = self:GetBulletSourcePos()
	if not pos or not ang then return end
	local forward = ang:Forward()
	owner:LagCompensation(true)
	self:ShootBullet(pos, forward, self.Primary.NumShots or 1, self.Primary.Spread or 0.01, 1)
	owner:LagCompensation(false)
	self:SetNextPrimaryFire(CurTime() + self.Primary.Delay)
	if SERVER then self:CreateShootRage() end
	if not self.Ironsighted and self.HipFireRecoil then
		local pitch = math.random() >= 0.5 and -self.HipFireRecoil or self.HipFireRecoil
		local yaw = math.random() >= 0.5 and -self.HipFireRecoil or self.HipFireRecoil
		local roll = math.random() >= 0.5 and -self.HipFireRecoil or self.HipFireRecoil
		owner:SetViewPunchAngles(Angle(pitch, yaw, roll))
	end

	if self.PostPrimaryAttack then self:PostPrimaryAttack() end
end

-- Проверка возможности атаки
function SWEP:CanPrimaryAttack()
	if self:GetSafetyStance() then return false end
	local owner = self:GetOwner()
	if not IsValid(owner) then return false end
	if qwb.isPlayerRunning(owner) then return false end
	if self:Clip1() <= 0 then
		self:EmitSound("weapons/clipempty_rifle.wav")
		self:SetNextPrimaryFire(CurTime() + 0.7)
		return false
	end

	if owner:WaterLevel() > 2 then return false end
	return true
end

-- Основной цикл оружия
function SWEP:Think()
	local owner = self:GetOwner()
	if not IsValid(owner) then return end
	-- Переключение режима безопасности
	if owner:KeyDown(IN_USE) and owner:KeyDown(IN_RELOAD) and (not self.SafetyStanceCD or CurTime() >= self.SafetyStanceCD) then
		self.SafetyStanceCD = CurTime() + 0.4
		self:SetSafetyStance(not self:GetSafetyStance())
	end

	-- Управление типом захвата
	local passiveHoldType = self.PassiveHoldType or self:CalculatePassiveHoldType()
	if qwb.isPlayerRunning(owner) and self:GetHoldType() ~= passiveHoldType then
		self:SetHoldType(passiveHoldType)
		if self.Ironsighted then self.Ironsighted = false end
	elseif not qwb.isPlayerRunning(owner) and not self:GetSafetyStance() and self:GetHoldType() == passiveHoldType then
		self:SetHoldType(self.HoldType)
	end

	-- Управление "Shoot Rage"
	self.ShootRageDelta = self.ShootRageDelta or 0
	if self.ShootRage and self.ShootRage > 0 then
		self.ShootRageDelta = self.ShootRageDelta + 0.2
		self.ShootRage = math.max(self.ShootRage - 0.05, 0)
	else
		self.ShootRageDelta = 0
	end

	-- Анимация отдачи
	if self.RecoilAnim then
		local bone1 = owner:LookupBone("ValveBiped.Bip01_R_Hand")
		local bone2 = owner:LookupBone("ValveBiped.Bip01_R_UpperArm")
		if bone1 and bone2 then
			local needle1 = Angle(self.Recoil / 50 * 90, 0, 0)
			local needle2 = Angle(0, (self.ShootRage or 0) * math.sin(self.ShootRageDelta or 0), 0)
			local currentAng1 = owner:GetManipulateBoneAngles(bone1)
			local newAng1 = LerpAngle(0.8, currentAng1, needle1)
			owner:ManipulateBoneAngles(bone1, newAng1)
			local currentAng2 = owner:GetManipulateBoneAngles(bone2)
			local newAng2 = LerpAngle(0.45, currentAng2, needle2)
			owner:ManipulateBoneAngles(bone2, newAng2)
			local ang = owner:GetManipulateBoneAngles(bone1)
			if math.abs(ang.p - needle1.p) <= 0.1 then
				self.RecoilAnim = nil
				self.RecoilAnimBack = true
			end
		end
	elseif self.RecoilAnimBack then
		local bone1 = owner:LookupBone("ValveBiped.Bip01_R_Hand")
		local bone2 = owner:LookupBone("ValveBiped.Bip01_R_UpperArm")
		if bone1 and bone2 then
			local needle1 = Angle(0, 0, 0)
			local needle2 = Angle(0, (self.ShootRage or 0) * math.sin(self.ShootRageDelta or 0), 0)
			local currentAng1 = owner:GetManipulateBoneAngles(bone1)
			local newAng1 = LerpAngle(0.1, currentAng1, needle1)
			owner:ManipulateBoneAngles(bone1, newAng1)
			local currentAng2 = owner:GetManipulateBoneAngles(bone2)
			local newAng2 = LerpAngle(0.1, currentAng2, needle2)
			owner:ManipulateBoneAngles(bone2, newAng2)
			local ang = owner:GetManipulateBoneAngles(bone1)
			if math.abs(ang.p - needle1.p) <= 0.1 then self.RecoilAnimBack = nil end
		end
	end
end
```
Почему иногда у оружия пули вылетают не прямо, а куда то влево вверх