Initial commit

This commit is contained in:
2026-03-15 14:54:49 +03:00
commit 64f8029c06
4027 changed files with 254888 additions and 0 deletions

10
addons/qwb/addon.json Normal file
View File

@@ -0,0 +1,10 @@
{
"title": "QWB - qurs' weapons base",
"type": "weapon",
"tags": [
"fun",
"roleplay",
"movie"
],
"ignore": []
}

View File

@@ -0,0 +1,30 @@
qwb = qwb or {}
local function includeClient(path)
if SERVER then
AddCSLuaFile(path)
end
if CLIENT then
include(path)
end
end
local function includeServer(path)
if SERVER then
include(path)
end
end
local function includeShared(path)
if SERVER then
AddCSLuaFile(path)
end
include(path)
end
includeServer('qwb/sv_init.lua')
includeShared('qwb/sh_init.lua')
includeClient('qwb/cl_init.lua')
includeClient('qwb/cl_spawnmenu.lua')

View File

@@ -0,0 +1,18 @@
AddCSLuaFile()
ENT.Type = 'anim'
ENT.Base = 'base_gmodentity'
ENT.PrintName = 'QWB Dummy'
ENT.Author = 'qurs'
ENT.Spawnable = false
ENT.AdminOnly = true
if SERVER then
function ENT:Initialize()
self:PhysicsInit(SOLID_NONE)
self:SetMoveType(MOVETYPE_NONE)
self:SetLocalAngularVelocity(Angle())
end
end

View File

@@ -0,0 +1,198 @@
stwFP = stwFP or {}
local fpEnabled = false
local nonFPWeapons = {
['weapon_physgun'] = true,
['gmod_tool'] = true,
['weapon_physcannon'] = true,
['gmod_camera'] = true,
}
function stwFP.hideHead(b)
local ply = LocalPlayer()
if b then
ply:ManipulateBoneScale(ply:LookupBone('ValveBiped.Bip01_Head1') or 0, Vector(0.01, 0.01, 0.01))
else
ply:ManipulateBoneScale(ply:LookupBone('ValveBiped.Bip01_Head1') or 0, Vector(1, 1, 1))
end
ply.headHided = b
end
function stwFP.blind(b)
if b then
hook.Add('RenderScreenspaceEffects', 'qwb.fp.setBlind', function()
DrawColorModify({
[ "$pp_colour_addr" ] = 0, -- no change
[ "$pp_colour_addg" ] = 0, -- no change
[ "$pp_colour_addb" ] = 0, -- no change
[ "$pp_colour_brightness" ] = -1,
[ "$pp_colour_contrast" ] = 1, -- no change
[ "$pp_colour_colour" ] = 1, -- no change
[ "$pp_colour_mulr" ] = 0, -- no change
[ "$pp_colour_mulg" ] = 0, -- no change
[ "$pp_colour_mulb" ] = 0, -- no change
})
end)
else
hook.Remove('RenderScreenspaceEffects', 'qwb.fp.setBlind')
end
end
function stwFP.startFP()
fpEnabled = true
if not LocalPlayer().headHided then
stwFP.hideHead(true)
end
local texturesHit = false
hook.Add('RenderScreenspaceEffects', 'qwb.fp.effects', function()
if not texturesHit then return end
DrawColorModify({
[ "$pp_colour_addr" ] = 0, -- no change
[ "$pp_colour_addg" ] = 0, -- no change
[ "$pp_colour_addb" ] = 0, -- no change
[ "$pp_colour_brightness" ] = -1,
[ "$pp_colour_contrast" ] = 1, -- no change
[ "$pp_colour_colour" ] = 1, -- no change
[ "$pp_colour_mulr" ] = 0, -- no change
[ "$pp_colour_mulg" ] = 0, -- no change
[ "$pp_colour_mulb" ] = 0, -- no change
})
end)
hook.Add('CalcView', 'qwb.fp', function(ply, origin, ang, fov, znear, zfar)
if not IsValid(LocalPlayer()) then return end
if ply ~= LocalPlayer() then return end
if not LocalPlayer():LookupBone('ValveBiped.Bip01_Head1') then return end
if ply:Alive() then
local pos, angles = LocalPlayer():GetBonePosition( LocalPlayer():LookupBone('ValveBiped.Bip01_Head1') )
local cameraPos = pos + ang:Right() * 0
cameraPos = cameraPos + ang:Up() * 4
cameraPos = cameraPos + ply:GetForward() * 1
local filter = player.GetAll()
filter[#filter + 1] = ply
filter[#filter + 1] = ply:GetActiveWeapon()
table.Add(filter, ents.FindByClass('gmod_sent_vehicle_fphysics_base'))
local tr = util.TraceHull({
start = pos,
endpos = cameraPos,
filter = filter,
mins = Vector(-4.5,-4.5,-4.5),
maxs = Vector(4.5, 4.5, 4.5),
})
cameraPos = tr.HitPos
if tr.Hit and not texturesHit then
texturesHit = true
elseif not tr.Hit and texturesHit then
texturesHit = false
end
-- if not qwb.ironsighted and qwb.IronSightLerp then
-- local sub = qwb.IronSightLerp - cameraPos
-- if math.abs(sub.x) > 1.5 or math.abs(sub.y) > 1.5 or math.abs(sub.z) > 1.5 then
-- qwb.IronSightLerp = Lerp(0.4, qwb.IronSightLerp, cameraPos)
-- else
-- qwb.IronSightLerp = nil
-- end
-- elseif qwb.ironsighted and not qwb.IronSightLerp then
-- qwb.IronSightLerp = cameraPos
-- end
return {
-- origin = qwb.IronSightLerp or cameraPos,
origin = cameraPos,
angles = Angle((ang.p > 65 and 65) or (ang.p < -65 and -65) or ang.p, ang.y + math.sin(SysTime() / 1.5) * 0.5, ang.r + math.sin(SysTime()) * 0.5),
fov = fov,
drawviewer = true,
}
else
if IsValid(ply:GetRagdollEntity()) then
if texturesHit then texturesHit = false end
local ragdoll = ply:GetRagdollEntity()
local bone = ragdoll:LookupBone('ValveBiped.Bip01_Head1') or 0
if ragdoll:GetManipulateBoneScale(bone) ~= Vector(0.01, 0.01, 0.01) then
ragdoll:ManipulateBoneScale(bone, Vector(0.01, 0.01, 0.01))
end
local pos, angles = ragdoll:GetBonePosition(bone)
return {
origin = pos,
angles = angles + Angle(-90,0,-90),
fov = fov,
drawviewer = false,
}
end
end
end)
hook.Add('InputMouseApply', 'qwb.fp', function(cmd, x, y, ang)
ang.p = math.Clamp(ang.p + y / 50, -65, 65)
ang.y = ang.y - x / 50
cmd:SetViewAngles(ang)
return true
end)
hook.Add('HUDShouldDraw', 'qwb.fp.crosshair', function(name)
if name == 'CHudCrosshair' then return false end
end)
end
function stwFP.stopFP()
hook.Remove('CalcView', 'qwb.fp')
hook.Remove('InputMouseApply', 'qwb.fp')
hook.Remove('RenderScreenspaceEffects', 'qwb.fp.effects')
fpEnabled = false
if LocalPlayer().headHided then
stwFP.hideHead(false)
end
end
hook.Add('qwb.fp.canEnableFP', 'qwb.fp', function()
if not IsValid(LocalPlayer()) then return end
if not IsValid(LocalPlayer():GetActiveWeapon()) then return end
if GetViewEntity() ~= LocalPlayer() then return false end
if nonFPWeapons[LocalPlayer():GetActiveWeapon():GetClass()] then return false end
end)
hook.Add('Think', 'qwb.fp.waiting', function()
if not LocalPlayer() or not IsValid(LocalPlayer()) or LocalPlayer() == NULL then return end
if hook.Run('qwb.fp.canEnableFP') ~= false and not fpEnabled then
stwFP.startFP()
elseif hook.Run('qwb.fp.canEnableFP') == false and fpEnabled then
stwFP.stopFP()
end
if LocalPlayer():Alive() and IsValid(LocalPlayer():GetRagdollEntity()) then
local ragdoll = LocalPlayer():GetRagdollEntity()
if ragdoll:GetManipulateBoneScale(ragdoll:LookupBone('ValveBiped.Bip01_Head1') or 0) ~= Vector(1, 1, 1) then
ragdoll:ManipulateBoneScale(ragdoll:LookupBone('ValveBiped.Bip01_Head1') or 0, Vector(1, 1, 1))
end
end
end)
timer.Create('qwb.fp.checkHead', 3.5, 0, function()
if LocalPlayer().headHided and LocalPlayer():GetManipulateBoneScale(LocalPlayer():LookupBone('ValveBiped.Bip01_Head1') or 0) ~= Vector(0.01, 0.01, 0.01) then
stwFP.hideHead(true)
end
end)

View File

@@ -0,0 +1,206 @@
local firstPersonMode = CreateClientConVar('qwb_firstperson', 0, true, false, 'FirstPerson Mode (from the hip)', 0, 2)
local firstPersonZNear = CreateClientConVar('qwb_firstperson_znear', 1.75, true, false, 'FirstPerson ZNear', 1, 5)
net.Receive('qwb.setIronsight', function()
local b = net.ReadBool()
qwb.ironsighted = b
end)
local angle_zero = Angle()
local firstPersonOffset1 = Vector()
local firstPersonOffset2 = Vector(-2, 0, 5)
local headDefaultScale = Vector(1, 1, 1)
local headScale = Vector(0.01, 0.01, 0.01)
local function hideHead(ply, boneID)
if not IsValid(ply) then return false end
boneID = boneID or ply:LookupBone('ValveBiped.Bip01_Head1')
if not boneID then return end
local curHeadScale = ply:GetManipulateBoneScale(boneID)
if curHeadScale ~= headScale then
ply:ManipulateBoneScale(boneID, headScale)
end
end
local function showHead(ply, boneID)
if not IsValid(ply) then return false end
boneID = boneID or ply:LookupBone('ValveBiped.Bip01_Head1')
if not boneID then return end
local curHeadScale = ply:GetManipulateBoneScale(boneID)
if curHeadScale ~= headDefaultScale then
ply:ManipulateBoneScale(boneID, headDefaultScale)
end
end
local function shouldChangeCalcView(ply)
if not IsValid(ply) then return false end
if GetViewEntity() ~= LocalPlayer() then return false end
if not ply:Alive() then return false end
local weap = ply:GetActiveWeapon()
if not IsValid(weap) or not weap.IsQWB then return false end
return true
end
local firstPersonModes = {
[0] = function(ply, origin, angles)
local att = ply:GetAttachment( ply:LookupAttachment('eyes') )
if not att then return end
local pos, ang = LocalToWorld(firstPersonOffset1, angle_zero, att.Pos, att.Ang)
return pos, ang
end,
[1] = function(ply, origin, angles)
local att = ply:GetAttachment( ply:LookupAttachment('anim_attachment_head') )
if not att then return end
local pos, ang = LocalToWorld(firstPersonOffset2, angle_zero, att.Pos, angles)
return pos, ang
end,
}
local function getSightPos(ply)
local weap = ply:GetActiveWeapon()
local att = ply:GetAttachment( ply:LookupAttachment('anim_attachment_rh') )
if not att then return end
local offset = weap.IronsightOffset
local angOffset = weap.IronsightAngle
if weap.Attachments and weap._attachments and weap._attachments.sights then
local id = weap._attachments.sights.id
local data = weap.Attachments.sights[id]
if data then
local camOffset = data.sightCameraOffset
if camOffset then
offset = offset + camOffset
end
local camAngOffset = data.sightCameraAngleOffset
if camAngOffset then
angOffset = angOffset + camAngOffset
end
end
end
return LocalToWorld(offset, angOffset or angle_zero, att.Pos, att.Ang)
end
local function nonSightCalcView(ply, origin, angles, fov, znear, zfar)
if not shouldChangeCalcView(ply) then
qwb.ironsightLerpProgress = nil
return
end
qwb.ironsightLerpProgress = qwb.ironsightLerpProgress or 0
qwb.ironsightLerpProgress = math.Clamp(qwb.ironsightLerpProgress - (FrameTime() * 4), 0, 1)
local mode = firstPersonMode:GetInt()
local getPosFunc = firstPersonModes[mode]
if not getPosFunc then return end
local pos, ang = getPosFunc(ply, origin, angles)
if not pos then return end
local sightPos = getSightPos(ply) or origin
pos = LerpVector(qwb.ironsightLerpProgress, pos, sightPos)
return {
origin = pos,
angles = ang,
fov = fov,
drawviewer = true,
znear = firstPersonZNear:GetFloat() or 1.75,
}
end
timer.Create('qwb.headCheck', 0.5, 0, function()
if not shouldChangeCalcView(LocalPlayer()) or (firstPersonMode:GetInt() ~= 1 and not qwb.ironsighted) then
showHead(LocalPlayer())
elseif shouldChangeCalcView(LocalPlayer()) and (firstPersonMode:GetInt() == 1 or qwb.ironsighted) then
hideHead(LocalPlayer())
end
end)
hook.Add('InputMouseApply', 'qwb.firstPerson', function(cmd, x, y, ang)
local mode = firstPersonMode:GetInt()
if mode ~= 1 then return end
if not shouldChangeCalcView(LocalPlayer()) then return end
ang.p = math.Clamp(ang.p + y / 50, -65, 65)
ang.y = ang.y - x / 50
cmd:SetViewAngles(ang)
return true
end)
hook.Add('CalcView', 'qwb.ironsight', function(ply, origin, angles, fov, znear, zfar)
if not qwb.ironsighted then return nonSightCalcView(ply, origin, angles, fov, znear, zfar) end
if not shouldChangeCalcView(ply) or qwb.isPlayerRunning(ply) then
qwb.ironsighted = nil
qwb.ironsightLerpProgress = nil
return
end
local pos, ang = getSightPos(ply)
qwb.ironsightLerpProgress = qwb.ironsightLerpProgress or 0
qwb.ironsightLerpProgress = math.Clamp(qwb.ironsightLerpProgress + (FrameTime() * 3), 0, 1)
local mode = firstPersonMode:GetInt()
local getFunc = firstPersonModes[mode]
local nonSightPos = getFunc and getFunc(ply, origin, angles) or origin
pos = LerpVector(qwb.ironsightLerpProgress, nonSightPos, pos)
local weap = ply:GetActiveWeapon()
local view = {
origin = pos,
angles = ang,
fov = fov,
znear = weap.IronsightZNear or 2.35,
drawviewer = true,
}
return view
end)
-- Thanks to octothorp team for this solution, which fixes camera shake
hook.Add('RenderScene', '_qwb', function(pos, angle, fov)
local camData = hook.Run('CalcView', LocalPlayer(), pos, angle, fov)
if not camData then return end
render.Clear(0, 0, 0, 255, true, true, true)
render.RenderView({
angles = camData.angles,
origin = camData.origin,
drawhud = true,
drawmonitors = true,
dopostprocess = true,
})
return true
end)
function qwb.fullyClearStencil()
render.SetStencilWriteMask( 0xFF )
render.SetStencilTestMask( 0xFF )
render.SetStencilReferenceValue( 0 )
render.SetStencilCompareFunction( STENCIL_ALWAYS )
render.SetStencilPassOperation( STENCIL_KEEP )
render.SetStencilFailOperation( STENCIL_KEEP )
render.SetStencilZFailOperation( STENCIL_KEEP )
render.ClearStencil()
end

View File

@@ -0,0 +1,37 @@
local lang = GetConVar('gmod_language'):GetString()
language.Add('qwb.tab', 'QWB')
if lang == 'ru' then
language.Add('qwb.options', 'Настройки')
language.Add('qwb.clientSettings', 'Клиентские настройки')
language.Add('qwb.serverSettings', 'Серверные настройки')
language.Add('qwb.firstPersonSelector.slider', 'Режим первого лица (от бедра)')
language.Add('qwb.firstPersonZNear.slider', 'Ограничение рендера от первого лица (ZNear)')
language.Add('qwb.realisticDamage.checkbox', 'Включить реалистичный урон')
language.Add('qwb.realisticDamage.checkbox.tooltip', 'Урон будет масштабироваться в зависимости от части тела, куда попадает пуля')
elseif lang == 'en' then
language.Add('qwb.options', 'Options')
language.Add('qwb.clientSettings', 'Client Settings')
language.Add('qwb.serverSettings', 'Server Settings')
language.Add('qwb.firstPersonSelector.slider', 'First person mode (from the hip)')
language.Add('qwb.firstPersonZNear.slider', 'Clipping a first person render (ZNear)')
language.Add('qwb.realisticDamage.checkbox', 'Enable realistic damage')
language.Add('qwb.realisticDamage.checkbox.tooltip', 'Damage will scale based on the part of the body the bullet hits')
end
hook.Add('AddToolMenuTabs', 'qwb', function()
spawnmenu.AddToolTab('qwb', '#qwb.tab', 'icon16/user_edit.png')
spawnmenu.AddToolCategory('qwb', 'qwb.options', '#qwb.options')
spawnmenu.AddToolMenuOption('qwb', 'qwb.options', 'qwb.clientSettings', '#qwb.clientSettings', '', '', function(pnl)
pnl:ClearControls()
pnl:NumSlider('#qwb.firstPersonSelector.slider', 'qwb_firstperson', 0, 2, 0):SetTooltip('#qwb.firstPersonSelector.slider')
pnl:NumSlider('#qwb.firstPersonZNear.slider', 'qwb_firstperson_znear', 1, 5, 2):SetTooltip('#qwb.firstPersonZNear.slider')
end)
spawnmenu.AddToolMenuOption('qwb', 'qwb.options', 'qwb.serverSettings', '#qwb.serverSettings', '', '', function(pnl)
pnl:ClearControls()
pnl:CheckBox('#qwb.realisticDamage.checkbox', 'qwb_realisticdamage'):SetTooltip('#qwb.realisticDamage.checkbox.tooltip')
end)
end)

View File

@@ -0,0 +1,5 @@
function qwb.isPlayerRunning(ply)
if not IsValid(ply) then return end
return ply:KeyDown(IN_SPEED) and ply:GetVelocity():LengthSqr() > 0
end

View File

@@ -0,0 +1,28 @@
util.AddNetworkString('qwb.setIronsight')
util.AddNetworkString('qwb.setAttachment')
util.AddNetworkString('qwb.removeAttachment')
util.AddNetworkString('qwb.muzzleFlashLight')
local realisticDamage = CreateConVar('qwb_realisticdamage', 1, FCVAR_NOTIFY, '', 0, 1)
local hitgroups = {
[HITGROUP_HEAD] = 10000,
[HITGROUP_STOMACH] = 1.5,
[HITGROUP_LEFTARM] = 0.2,
[HITGROUP_RIGHTARM] = 0.2,
[HITGROUP_LEFTLEG] = 0.3,
[HITGROUP_RIGHTLEG] = 0.3,
}
hook.Add('ScalePlayerDamage', 'qwb.damage', function( ply, hitgroup, dmginfo )
if not realisticDamage:GetBool() then return end
local attacker = dmginfo:GetAttacker()
if not IsValid(attacker) or not IsValid(attacker:GetActiveWeapon()) then return end
local weap = attacker:GetActiveWeapon()
if not weap.IsQWB then return end
local scale = hitgroups[hitgroup]
if scale then dmginfo:ScaleDamage(scale) end
end)

View File

@@ -0,0 +1,43 @@
AddCSLuaFile()
SWEP.Base = 'weapon_base_qwb'
SWEP.WorldModel = 'models/weapons/w_snip_awp.mdl'
SWEP.PrintName = 'AWP'
SWEP.Category = 'qurs\' weapons base'
SWEP.Spawnable = true
SWEP.AdminOnly = false
SWEP.IronsightOffset = Vector(-3.1, -0.8, 6.7)
SWEP.IronsightAngle = Angle(-5, -2, 0)
SWEP.Primary.Sound = 'Weapon_AWP.Single'
SWEP.Primary.Damage = 85
SWEP.Primary.NumShots = 1
SWEP.Primary.Spread = 0.005
SWEP.Primary.Delay = 1
SWEP.Primary.ClipSize = 8
SWEP.Primary.DefaultClip = 0
SWEP.Primary.Automatic = false
SWEP.Primary.Ammo = '357'
SWEP.Recoil = 8
SWEP.HorizontalRecoil = 8
SWEP.HipFireRecoil = 5
SWEP.HoldType = 'ar2'
SWEP.ScopeOffset = Vector(-36.9, 3.47, -0.955)
SWEP.ScopeSize = {184, 186}
SWEP.ScopeMat = Material('materials/qwb/scope/awp.png')
SWEP.ScopeFOV = 10
SWEP.ShellType = '762Nato'
SWEP.ShellOffset = Vector(-33, 1, -0.5)
SWEP.ShellVelocity = 75
SWEP.AimSound = Sound('weapons/ammopickup.wav')

View File

@@ -0,0 +1,36 @@
AddCSLuaFile()
SWEP.Base = 'weapon_base_qwb'
SWEP.WorldModel = 'models/weapons/w_pist_glock18.mdl'
SWEP.PrintName = 'Glock-18'
SWEP.Category = 'qurs\' weapons base'
SWEP.Spawnable = true
SWEP.AdminOnly = false
SWEP.IronsightOffset = Vector(-10, -1.2, 3.95)
SWEP.IronsightAngle = Angle(0, 2, 0)
SWEP.Primary.Sound = 'Weapon_Glock.Single'
SWEP.Primary.Damage = 20
SWEP.Primary.NumShots = 1
SWEP.Primary.Spread = 0.01
SWEP.Primary.Delay = 0.12
SWEP.Primary.ClipSize = 12
SWEP.Primary.DefaultClip = 0
SWEP.Primary.Automatic = false
SWEP.Primary.Ammo = 'Pistol'
SWEP.Recoil = 5.5
SWEP.HorizontalRecoil = 10
SWEP.HoldType = 'revolver'
SWEP.ShellType = '9mm'
SWEP.ShellOffset = Vector(-5, 1, -0.5)
SWEP.ShellVelocity = -50
SWEP.AimSound = Sound('weapons/ammopickup.wav')

View File

@@ -0,0 +1,41 @@
AddCSLuaFile()
SWEP.Base = 'weapon_base_qwb'
SWEP.WorldModel = 'models/weapons/w_shot_m3super90.mdl'
SWEP.PrintName = 'M3 Super'
SWEP.Category = 'qurs\' weapons base'
SWEP.Spawnable = true
SWEP.AdminOnly = false
SWEP.IronsightOffset = Vector(-2.5, -0.95, 4.7)
SWEP.IronsightAngle = Angle(-5, -2, 0)
SWEP.IronsightZNear = 1
SWEP.Primary.Sound = 'Weapon_M3.Single'
SWEP.Primary.Damage = 6
SWEP.Primary.NumShots = 8
SWEP.Primary.Spread = 0.1
SWEP.Primary.Delay = 1
SWEP.Primary.ClipSize = 6
SWEP.Primary.DefaultClip = 0
SWEP.Primary.Automatic = false
SWEP.Primary.Ammo = 'Buckshot'
SWEP.Recoil = 15
SWEP.HorizontalRecoil = 25
SWEP.HipFireRecoil = 10
SWEP.HoldType = 'ar2'
SWEP.ShellType = '12Gauge'
SWEP.ShellOffset = Vector(-20, 1, -1.5)
SWEP.ShellVelocity = 100
SWEP.AimSound = Sound('weapons/ammopickup.wav')
SWEP.ShootPos = Vector(20, -0.8, 7.3)

View File

@@ -0,0 +1,69 @@
AddCSLuaFile()
SWEP.Base = 'weapon_base_qwb'
SWEP.WorldModel = 'models/weapons/w_smg_ump45.mdl'
SWEP.PrintName = 'UMP-45'
SWEP.Category = 'qurs\' weapons base'
SWEP.Spawnable = true
SWEP.AdminOnly = false
SWEP.IronsightOffset = Vector(-6, -0.88, 5.7)
SWEP.IronsightAngle = Angle(0, -2, 0)
SWEP.IronsightZNear = 0.4
SWEP.Primary.Sound = 'Weapon_UMP45.Single'
SWEP.Primary.Damage = 5
SWEP.Primary.NumShots = 1
SWEP.Primary.Spread = 0.01
SWEP.Primary.Delay = 0.1
SWEP.Primary.ClipSize = 30
SWEP.Primary.DefaultClip = 0
SWEP.Primary.Automatic = true
SWEP.Primary.Ammo = 'SMG1'
SWEP.Recoil = 2
SWEP.HorizontalRecoil = 6.5
SWEP.HoldType = 'smg'
SWEP.ShellType = '57'
SWEP.ShellOffset = Vector(-5, 1, 1.5)
SWEP.ShellVelocity = 65
SWEP.AimSound = Sound('weapons/ammopickup.wav')
-- Attachments
SWEP.Attachments = {
sights = {
holo_sight = {
name = 'Holo sight',
mdl = 'models/attachment/crysis_holo.mdl',
pos = Vector(-3, 1.3, 6.95),
ang = Angle(-10, 0, 0),
scale = 1,
sightCameraOffset = Vector(0, 0, 0.5),
sightCameraAngleOffset = Angle(),
},
optical_sight = {
name = 'Optical sight',
mdl = 'models/attachment/crysis_holo.mdl',
pos = Vector(-3, 1.3, 6.95),
ang = Angle(-10, 0, 0),
scale = 1,
sightCameraOffset = Vector(0, 0, 0.5),
sightCameraAngleOffset = Angle(),
opticPos = Vector(3.5, -0.3, 1.75), -- the position of the rendered crosshair relative to the attachment model
opticAng = Angle(-90, 0, 0),
opticSize = {80, 80},
opticFOV = 5,
},
},
}

View File

@@ -0,0 +1,36 @@
AddCSLuaFile()
SWEP.Base = 'weapon_base_qwb'
SWEP.WorldModel = 'models/weapons/w_pist_usp.mdl'
SWEP.PrintName = 'USP'
SWEP.Category = 'qurs\' weapons base'
SWEP.Spawnable = true
SWEP.AdminOnly = false
SWEP.IronsightOffset = Vector(-10, -1.1, 4)
SWEP.IronsightAngle = Angle(0, 3, 0)
SWEP.Primary.Sound = 'Weapon_USP.Single'
SWEP.Primary.Damage = 15
SWEP.Primary.NumShots = 1
SWEP.Primary.Spread = 0.01
SWEP.Primary.Delay = 0.15
SWEP.Primary.ClipSize = 15
SWEP.Primary.DefaultClip = 0
SWEP.Primary.Automatic = false
SWEP.Primary.Ammo = 'Pistol'
SWEP.Recoil = 5
SWEP.HorizontalRecoil = 8
SWEP.HoldType = 'revolver'
SWEP.ShellType = '9mm'
SWEP.ShellOffset = Vector(-4.5, 1, -1)
SWEP.ShellVelocity = 60
SWEP.AimSound = Sound('weapons/ammopickup.wav')

View File

@@ -0,0 +1,114 @@
function SWEP:OnRemove()
for k, v in pairs(self._attachments or {}) do
if IsValid(v.csEnt) then v.csEnt:Remove() end
end
end
local function drawOpticAttach(self, flags)
local weap = self.parent
if not IsValid(weap) then return end
local ply = self:GetParent()
if not IsValid(ply) then return end
if ply:GetActiveWeapon() ~= weap then return end
self:DrawModel(flags)
if not qwb.ironsighted then return end
local scopeMat = self.opticMat
local attID = weap:LookupAttachment('muzzle')
if not attID then return end
local att = weap:GetAttachment(attID)
if not att then return end
local pos, ang = LocalToWorld(self.opticPos, self.opticAng or angle2, self:GetPos(), att.Ang)
local w, h = unpack(self.opticSize)
local radius = math.max(w, h)
local semiRadius = radius * 0.5
qwb.fullyClearStencil()
render.SetStencilEnable(true)
render.SetStencilCompareFunction(STENCIL_ALWAYS)
render.SetStencilFailOperation(STENCIL_ZERO)
render.SetStencilZFailOperation(STENCIL_ZERO)
render.SetStencilPassOperation(STENCIL_REPLACE)
render.SetStencilReferenceValue(1)
cam.Start3D2D(pos, ang, 0.01)
draw.NoTexture()
surface.SetDrawColor(color_black)
draw_Circle(semiRadius, semiRadius, semiRadius, 30)
cam.End3D2D()
render.SetStencilCompareFunction(STENCIL_EQUAL)
cam.Start3D2D(pos, ang, 0.01)
surface.SetDrawColor(color_white)
surface.SetMaterial(mat)
surface.DrawTexturedRect(0, 0, w, h)
surface.SetDrawColor(color_white)
surface.SetMaterial(scopeMat)
surface.DrawTexturedRect(0, 0, w, h)
cam.End3D2D()
render.SetStencilEnable(false)
end
net.Receive('qwb.setAttachment', function()
local weap = net.ReadEntity()
if not IsValid(weap) then return end
local attachType, attachID = net.ReadString(), net.ReadString()
local data = weap.Attachments[attachType] and weap.Attachments[attachType][attachID]
if not data then return end
weap._attachments = weap._attachments or {}
weap._attachments[attachType] = weap._attachments[attachType] or {}
local tbl = weap._attachments[attachType]
tbl.id = attachID
if IsValid(tbl.csEnt) then tbl.csEnt:Remove() end
local owner = weap:GetOwner()
if not IsValid(tbl.csEnt) then
tbl.csEnt = ClientsideModel(data.mdl)
tbl.csEnt:SetParent(LocalPlayer(), LocalPlayer():LookupAttachment('anim_attachment_rh'))
tbl.csEnt:SetLocalPos(data.pos)
tbl.csEnt:SetLocalAngles(data.ang)
tbl.csEnt:SetModelScale(data.scale or 1)
tbl.csEnt.parent = weap
if data.opticPos and owner == LocalPlayer() then
tbl.csEnt.opticPos = data.opticPos
tbl.csEnt.opticAng = data.opticAng
tbl.csEnt.opticSize = data.opticSize
tbl.csEnt.opticMat = data.opticMat or weap.DefaultScopeMat
tbl.csEnt.RenderOverride = drawOpticAttach
end
tbl.csEnt:Spawn()
end
end)
net.Receive('qwb.removeAttachment', function()
local weap = net.ReadEntity()
if not IsValid(weap) then return end
if not weap._attachments then return end
local attachType = net.ReadString()
if not weap._attachments[attachType] then return end
if IsValid(weap._attachments[attachType].csEnt) then weap._attachments[attachType].csEnt:Remove() end
weap._attachments[attachType] = nil
end)

View File

@@ -0,0 +1,38 @@
local color_bg = Color(0, 0, 0, 190)
function SWEP:OpenAttMenu()
self._attMenu = vgui.Create('DPanel')
local pnl = self._attMenu
pnl:SetSize(ScrW(), ScrH())
function pnl:Paint(w, h)
surface.SetDrawColor(color_bg)
surface.DrawRect(0, 0, w, h)
end
qwb.attMenuOpened = true
end
function SWEP:CloseAttMenu()
self._attMenu:Remove()
self._attMenu = nil
qwb.attMenuOpened = nil
end
function SWEP:ToggleAttMenu()
if IsValid(self._attMenu) then
self:CloseAttMenu()
else
self:OpenAttMenu()
end
end
-- hook.Add('OnContextMenuOpen', 'qwb.att.menu', function()
-- local weap = LocalPlayer():GetActiveWeapon()
-- if not IsValid(weap) then return end
-- if not weap.IsQWB then return end
-- weap:ToggleAttMenu()
-- end)

View File

@@ -0,0 +1,128 @@
include('shared.lua')
include('sh_att.lua')
include('cl_att.lua')
include('cl_att_menu.lua')
local size = 512
local RT = GetRenderTarget('qwb_scope', size, size)
local mat = CreateMaterial('qwb_scope', 'UnlitGeneric', {
['$basetexture'] = RT:GetName(),
['$translucent'] = 1,
['$vertexcolor'] = 1,
})
local vector_origin = Vector()
local angle1, angle2 = Angle(0, 0, -90), Angle(-90, 0, 0)
local color_white, color_black = Color(255, 255, 255), Color(0, 0, 0)
local function draw_Circle( x, y, radius, seg )
local cir = {}
table.insert( cir, { x = x, y = y, u = 0.5, v = 0.5 } )
for i = 0, seg do
local a = math.rad( ( i / seg ) * -360 )
table.insert( cir, { x = x + math.sin( a ) * radius, y = y + math.cos( a ) * radius, u = math.sin( a ) / 2 + 0.5, v = math.cos( a ) / 2 + 0.5 } )
end
local a = math.rad( 0 ) -- This is needed for non absolute segment counts
table.insert( cir, { x = x + math.sin( a ) * radius, y = y + math.cos( a ) * radius, u = math.sin( a ) / 2 + 0.5, v = math.cos( a ) / 2 + 0.5 } )
surface.DrawPoly( cir )
end
hook.Add('Think', 'qwb.renderScope', function()
if not qwb.ironsighted then return end
local weap = LocalPlayer():GetActiveWeapon()
if not IsValid(weap) or not weap.IsQWB then return end
local sightData = weap:GetAttach('sights')
if not weap.ScopeOffset and (not sightData or not IsValid(sightData.csEnt) or not sightData.csEnt.opticPos) then return end
local att = weap:GetAttachment( weap:LookupAttachment('muzzle') )
if not att then return end
local pos, ang = att.Pos, att.Ang
local _, scopeAng = LocalToWorld( vector_origin, angle1, pos, ang )
local data = sightData and weap.Attachments and weap.Attachments.sights and weap.Attachments.sights[ sightData.id ]
render.PushRenderTarget(RT)
render.RenderView({
origin = pos,
angles = scopeAng,
fov = weap.ScopeFOV or (data and data.opticFOV) or 10,
})
render.PopRenderTarget()
end)
net.Receive('qwb.muzzleFlashLight', function()
local weap = net.ReadEntity()
if not IsValid(weap) then return end
local dlight = DynamicLight( weap:EntIndex() )
if dlight then
dlight.pos = weap:GetBulletSourcePos()
dlight.r = 255
dlight.g = 145
dlight.b = 10
dlight.brightness = 1
dlight.Decay = 6666
dlight.Size = 512
dlight.DieTime = CurTime() + 0.2
end
end)
function SWEP:DrawWorldModel(flags)
self:DrawModel(flags)
if not self.ScopeOffset or not self.ScopeSize then return end
if not qwb.ironsighted then return end
local owner = self:GetOwner()
if not IsValid(owner) then return end
if owner ~= LocalPlayer() then return end
local scopeMat = self.ScopeMat or self.DefaultScopeMat
local att = self:GetAttachment( self:LookupAttachment('muzzle') )
if not att then return end
local pos, ang = LocalToWorld(self.ScopeOffset, angle2, att.Pos, att.Ang)
local w, h = unpack(self.ScopeSize)
local radius = math.max(w, h)
local semiRadius = radius * 0.5
qwb.fullyClearStencil()
render.SetStencilEnable(true)
render.SetStencilCompareFunction(STENCIL_ALWAYS)
render.SetStencilFailOperation(STENCIL_ZERO)
render.SetStencilZFailOperation(STENCIL_ZERO)
render.SetStencilPassOperation(STENCIL_REPLACE)
render.SetStencilReferenceValue(1)
cam.Start3D2D(pos, ang, 0.01)
draw.NoTexture()
surface.SetDrawColor(color_black)
draw_Circle(semiRadius, semiRadius, semiRadius, 30)
cam.End3D2D()
render.SetStencilCompareFunction(STENCIL_EQUAL)
cam.Start3D2D(pos, ang, 0.01)
surface.SetDrawColor(color_white)
surface.SetMaterial(mat)
surface.DrawTexturedRect(0, 0, w, h)
surface.SetDrawColor(color_white)
surface.SetMaterial(scopeMat)
surface.DrawTexturedRect(0, 0, w, h)
cam.End3D2D()
render.SetStencilEnable(false)
end
function SWEP:SecondaryAttack()
end

View File

@@ -0,0 +1,35 @@
AddCSLuaFile('cl_init.lua')
AddCSLuaFile('shared.lua')
AddCSLuaFile('sh_att.lua')
AddCSLuaFile('cl_att.lua')
AddCSLuaFile('cl_att_menu.lua')
include('shared.lua')
include('sh_att.lua')
include('sv_att.lua')
function SWEP:CreateShootRage()
if not self.ShootRage then self.ShootRage = 0 end
self.ShootRage = self.ShootRage + self.HorizontalRecoil / 15
end
function SWEP:SecondaryAttack()
if self:GetSafetyStance() then return end
if not IsValid(self:GetOwner()) then return end
local owner = self:GetOwner()
if qwb.isPlayerRunning(owner) then return end
self.Ironsighted = not self.Ironsighted
if self.AimSound then
owner:EmitSound(self.AimSound, nil, nil, self.AimSoundVolume or 0.25)
end
net.Start('qwb.setIronsight')
net.WriteBool(self.Ironsighted)
net.Send(owner)
end

View File

@@ -0,0 +1,3 @@
function SWEP:GetAttach(att)
return self._attachments and self._attachments[att]
end

View File

@@ -0,0 +1,356 @@
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',
}
-- Thanks to octothorp team for this solution, which calculates the shot position
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)
self:GetOwner():EmitSound(self.SafetyStanceSound)
if new == true 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 lpos, lang = unpack( defaultBulletPosAng[ self:GetHoldType() ] or defaultBulletPosAng.default )
self._sourceLocalMuzzlePos = self.ShootPos or lpos
self._sourceLocalMuzzleAng = self.ShootAng or lang
if CLIENT then return end
self:SetLocalMuzzlePos(self._sourceLocalMuzzlePos)
self:SetLocalMuzzleAng(self._sourceLocalMuzzleAng)
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 att = owner:GetAttachment( owner:LookupAttachment('anim_attachment_rh') )
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 = {}
bullet.Num = num_bullets
bullet.Src = src
bullet.Dir = dir
bullet.Spread = Vector( aimcone, aimcone, 0 )
bullet.Tracer = tracer or 5
bullet.Force = 1
bullet.Damage = self.Primary.Damage
bullet.AmmoType = self.Primary.Ammo
bullet.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, 0, 1) -- pre-last self.Primary.Spread or 0.01
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 end
local owner = self:GetOwner()
if not IsValid(owner) then return end
if qwb.isPlayerRunning(owner) then return 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(self:GetOwner()) 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
if not self.ShootRageDelta then self.ShootRageDelta = 0 end
if self.ShootRage and self.ShootRage > 0 then
self.ShootRageDelta = self.ShootRageDelta + 0.2
self.ShootRage = self.ShootRage - 0.05
if self.ShootRage < 0 then self.ShootRage = 0 end
else
self.ShootRageDelta = 0
end
if self.RecoilAnim then
local bone = owner:LookupBone('ValveBiped.Bip01_R_Hand')
local bone2 = owner:LookupBone('ValveBiped.Bip01_R_UpperArm')
local needle = Angle(self.Recoil / 50 * 90, 0, 0)
local needle2 = Angle(0, (self.ShootRage or 0) * math.sin(self.ShootRageDelta or 0), 0)
owner:ManipulateBoneAngles( bone, LerpAngle(0.8, owner:GetManipulateBoneAngles(bone), needle), true )
owner:ManipulateBoneAngles( bone2, LerpAngle(0.45, owner:GetManipulateBoneAngles(bone2), needle2), true )
local ang = owner:GetManipulateBoneAngles(bone)
if math.abs(ang[1] - needle[1]) <= 0.1 then
self.RecoilAnim = nil
self.RecoilAnimBack = true
end
elseif self.RecoilAnimBack then
local bone = owner:LookupBone('ValveBiped.Bip01_R_Hand')
local bone2 = owner:LookupBone('ValveBiped.Bip01_R_UpperArm')
local needle = Angle(0, 0, 0)
local needle2 = Angle(0, (self.ShootRage or 0) * math.sin(self.ShootRageDelta or 0), 0)
owner:ManipulateBoneAngles( bone, LerpAngle(0.1, owner:GetManipulateBoneAngles(bone), needle), true )
owner:ManipulateBoneAngles( bone2, LerpAngle(0.1, owner:GetManipulateBoneAngles(bone2), needle2), true )
local ang = owner:GetManipulateBoneAngles(bone)
if math.abs(ang[1] - needle[1]) <= 0.1 then
self.RecoilAnimBack = nil
end
end
-- if CLIENT then
-- local bone = owner:LookupBone('ValveBiped.Bip01_R_Hand')
-- local ang = self:GetBoneAng()
-- owner:ManipulateBoneAngles(bone, ang)
-- end
-- if CLIENT then return end
-- if owner.RecoilAnim then
-- local curAng = self:GetLocalMuzzleAng()
-- local sourceAng = self._sourceLocalMuzzleAng
-- local needle = Angle(sourceAng[1] - (self.Recoil / 50 * 90), curAng[2], curAng[3])
-- self:SetLocalMuzzleAng( LerpAngle(0.8, self:GetLocalMuzzleAng(), needle) )
-- local ang = self:GetLocalMuzzleAng()
-- if math.abs(ang[1] - needle[1]) <= 0.1 then
-- owner.RecoilAnim = nil
-- owner.RecoilAnimBack = true
-- end
-- elseif owner.RecoilAnimBack then
-- local needle = self._sourceLocalMuzzleAng
-- self:SetLocalMuzzleAng( LerpAngle(0.025, self:GetLocalMuzzleAng(), needle) )
-- local ang = self:GetLocalMuzzleAng()
-- if math.abs(ang[1] - needle[1]) <= 0.1 then
-- owner.RecoilAnimBack = nil
-- end
-- end
end

View File

@@ -0,0 +1,52 @@
function SWEP:CanAttach(attachType, attachID)
return true
end
function SWEP:SetAttach(attachType, attachID)
if not self.Attachments[attachType] or not self.Attachments[attachType][attachID] then return end
self._attachments = self._attachments or {}
if self._attachments[attachType] == attachID then return end
if not self:CanAttach(attachType, attachID) then return end
self._attachments[attachType] = attachID
net.Start('qwb.setAttachment')
net.WriteEntity(self)
net.WriteString(attachType)
net.WriteString(attachID)
net.Broadcast()
end
function SWEP:RemoveAttach(attachType)
if not self._attachments then return end
if not self._attachments[attachType] then return end
self._attachments[attachType] = nil
net.Start('qwb.removeAttachment')
net.WriteEntity(self)
net.WriteString(attachType)
net.Broadcast()
end
net.Receive('qwb.setAttachment', function(_, ply)
local weap = ply:GetActiveWeapon()
if not IsValid(weap) or not weap.IsQWB or not weap.SetAttach then return end
local attachType, attachID = net.ReadString(), net.ReadString()
if not attachType or not attachID then return end
weap:SetAttach(attachType, attachID)
end)
net.Receive('qwb.removeAttachment', function(_, ply)
local weap = ply:GetActiveWeapon()
if not IsValid(weap) or not weap.IsQWB or not weap.RemoveAttach then return end
local attachType = net.ReadString()
if not attachType then return end
weap:RemoveAttach(attachType)
end)

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB