· 9 years ago · Dec 24, 2016, 05:36 AM
1-- FIXME: Splash effect doesn't show when scoped
2-- FIXME: Smoke isn't removed
3-- FIXME: Change to BipodDeploy
4-- FIXME: Bipod reloading/empty anims
5
6--- Base
7-- This is the superclass
8SWEP.Base = "weapon_gs_base"
9SWEP.GSWeapon = true -- For hooks to know
10
11--- Selection/Menu
12SWEP.PrintName = "GSBase" -- Display name
13SWEP.Author = "Teduken" -- This isn't used by default for weapon selection panels
14SWEP.Category = "Other"
15
16SWEP.Spawnable = true -- Displays the weapon in the spawn menu. This must be defined in every weapon!
17SWEP.AdminOnly = false -- Restricts weapon spawning to admin+
18
19SWEP.Slot = 0 -- Key (minus one) to press to get to the weapon's category in the weapon selection
20SWEP.SlotPos = 0 -- Category in the weapon selection (minus one) this weapon appears in
21
22--- Weapon demeanour
23SWEP.ViewModel = "models/weapons/v_DC17M_BR.mdl" -- First-person model of the weapon
24SWEP.ShouldIdle = false -- Play idle animations on the first view model
25SWEP.UseHands = false -- If the gamemode supports it, show player model hands on the weapon (c_models only)
26
27SWEP.ViewModel1 = "" -- Second first-person model: changing this to anything besides an empty string will display it
28SWEP.ShouldIdle1 = true
29SWEP.UseHands1 = false
30
31SWEP.ViewModel2 = ""
32SWEP.ShouldIdle2 = true
33SWEP.UseHands2 = false
34
35SWEP.WorldModel = "models/weapons/w_DC17M_BR.mdl" -- Third-person view of the weapon
36SWEP.SilencerModel = "" -- World model to use when the weapon is silenced
37SWEP.DroppedModel = "" -- World model to use when the weapon is dropped. Silencer model takes priotiy
38SWEP.ReloadModel = "" -- World model to use during reload. Silencer model takes priority
39SWEP.HoldType = "ar2" -- How the player should hold the weapon in third-person http://wiki.garrysmod.com/page/Hold_Types
40SWEP.HolsterAnimation = true -- Play an animation when the weapon is being holstered. Only works if the weapon has a holster animation
41
42SWEP.Weight = 0 -- Weight in automatic weapon selection
43-- There are two weapon switching algorithms:
44-- *Singleplay (true) - Switch to the weapon with the highest weight
45-- *Multiplay (false) - Try and find a weapon with the same weight, otherwise, fallback to highest
46SWEP.HighWeightPriority = false
47
48SWEP.Activities = { -- Default activity events
49 -- event = ACT_VM_EVENT,
50 --[[event2 = {
51 ACT_VM_EVENT1, -- Event to use for the first viewmodel
52 ACT_VM_EVENT2, -- Event to use for the second viewmodel
53 -- Third viewmodel will use ACT_VM_EVENT1 (fallback to viewmodel one) since a third key is not defined
54 rate = 0.7, -- Playback rate for the first and second viewmodels
55 rate3 = 2, -- Playback rate for the third viewmodel. The "rate#" key has priority over the "rate" key for a specific viewmodel
56 idle1 = 5, -- Time until idle for the first view model
57 idle2 = {6, 10} -- Time until idle for the second view model. A float is randomly selected in the provided range
58 -- The third viewmodel will use the sequence duration of its activity for its idle time
59 -- Setting the "idle" key, like "rate", will set the idle time for all viewmodels unless "idle#" is defined
60 }]]
61 shoot = ACT_VM_PRIMARYATTACK,
62 shoot_empty = ACT_VM_DRYFIRE, -- Used when the last bullet in the clip is fired
63 altfire = ACT_VM_SECONDARYATTACK,
64 silence = ACT_VM_ATTACH_SILENCER,
65 reload = ACT_VM_RELOAD,
66 reload_empty = ACT_VM_RELOAD_EMPTY, -- Used when the weapon is reloaded with an empty clip
67 empty = ACT_INVALID, -- Used when the weapon fires with no ammo in the clip
68 draw = ACT_VM_DRAW,
69 draw_empty = ACT_VM_DRAW_EMPTY,
70 holster = ACT_VM_HOLSTER,
71 holster_empty = ACT_VM_HOLSTER_EMPTY,
72 idle = ACT_VM_IDLE,
73 idle_empty = ACT_VM_IDLE_EMPTY,
74 burst = ACT_VM_PRIMARYATTACK,
75 pump = ACT_SHOTGUN_PUMP,
76 -- For silencers. If a silenced activity isn't available, the weapon will fallback to the non-silenced version
77 s_shoot = ACT_VM_PRIMARYATTACK_SILENCED,
78 s_shoot_empty = ACT_VM_DRYFIRE_SILENCED,
79 s_silence = ACT_VM_DETACH_SILENCER,
80 s_reload = ACT_VM_RELOAD_SILENCED,
81 s_draw = ACT_VM_DRAW_SILENCED,
82 s_idle = ACT_VM_IDLE_SILENCED,
83 -- For single reloading
84 reload_start = ACT_SHOTGUN_RELOAD_START,
85 reload_finish = ACT_SHOTGUN_RELOAD_FINISH,
86 -- For melee weapons
87 hit = ACT_VM_HITCENTER,
88 hit_alt = ACT_VM_HITCENTER2,
89 miss = ACT_VM_MISSCENTER,
90 miss_alt = ACT_VM_MISSCENTER2,
91 -- For grenades
92 pullback = ACT_VM_PULLBACK_HIGH,
93 throw = ACT_VM_THROW,
94 deploy = ACT_VM_DEPLOY,
95 deploy_empty = ACT_VM_DEPLOY_EMPTY,
96 d_deploy = ACT_VM_UNDEPLOY,
97 d_deploy_empty = ACT_VM_UNDEPLOY_EMPTY,
98 d_draw = ACT_VM_DRAW_DEPLOYED,
99 d_shoot = ACT_VM_PRIMARYATTACK_DEPLOYED,
100 d_reload = ACT_VM_RELOAD_DEPLOYED,
101 d_idle = ACT_VM_IDLE_DEPLOYED,
102 d_idle_empty = ACT_VM_IDLE_DEPLOYED_EMPTY
103}
104
105SWEP.Sounds = { -- Default sound events. If a sound isn't available, nothing will play
106 --event = "Sound.Scape",
107 --[[event2 = {
108 pitch = {50, 100},
109 sound = "sound2.wav"
110 },
111 event3 = {
112 "sound3.wav", -- Sound to use for the first viewmodel
113 nil, -- The second viewmodel will not play any sound for this event
114 { -- Sound to use for the third viewmodel
115 pitch = {10, 100},
116 sound = "Sound.Scape2"
117 }
118 },]]
119 draw = "",
120 shoot = "weapons/DC17M_BR_fire.wav",
121 altfire = "",
122 silence = "",
123 zoom = "",
124 burst = "",
125 reload = "weapons/DC17M_BR_reload.wav",
126 empty = "Weapon_Pistol.Empty",
127 holster = "",
128 pump = "",
129 -- For silencers
130 s_draw = "",
131 s_shoot = "Weapon_USP.SilencedShot",
132 s_altfire = "",
133 s_silence = "",
134 s_reload = "",
135 s_empty = "",
136 s_holster = "",
137 -- For single reloading
138 reload_start = "",
139 reload_finish = "",
140 -- For melee weapons
141 hit = "Weapon_Crowbar.Melee_Hit",
142 hitworld = "Weapon_Crowbar.Melee_HitWorld",
143 miss = "Weapon_Crowbar.Single",
144 -- For grenades
145 pullback = "",
146 throw = ""
147}
148
149--- Weapon behaviour
150SWEP.Primary = {
151 Ammo = "SMG1", -- Ammo type declared by game.GetAmmoType or exists by default http://wiki.garrysmod.com/page/Default_Ammo_Types
152 ClipSize = 30, -- Max amount of ammo in clip
153 DefaultClip = 120, -- Amount of ammo weapon spawns with
154 Automatic = true, -- Continously runs PrimaryAttack with the mouse held down
155 Bullets = 1, -- How many bullets are shot by FireBullets
156 Damage = 42, -- Bullet/melee damage // Douglas Adams 1952 - 2001
157 Force = 1, -- Force scale of shot/swing
158 Range = MAX_TRACE_LENGTH, -- Bullet/melee distance
159 Cooldown = 0.15, -- Time between firing
160 WalkSpeed = 1, -- Walk speed multiplier to use when the weapon is deployed
161 RunSpeed = 1, -- Run speed multiplier to use when the weapon is deployed
162 FireUnderwater = true, -- Allows firing underwater
163 FireInAir = true, -- Allows firing while off the ground
164 InterruptReload = true, -- Allows interrupting a reload to shoot
165 AutoReloadOnEmpty = false, -- Automatically reload if the clip is empty and the mouse is not being held
166 ReloadOnEmptyFire = true, -- Reload if the weapon is fired with an empty clip
167 Spread = vector_origin, -- Bullet spread vector
168 SpreadBias = 0.5, -- Amount of variance on the spread. Between -1 and 1
169 TracerFreq = 2, -- How often the tracer effect should show - (1 / SWEP.TracerFreq) frequency
170 TracerName = "effect_sw_laser_blue" -- Tracer effect to use
171 Delay = -1 }
172
173
174SWEP.Secondary = {
175 Ammo = "",
176 ClipSize = -1,
177 DefaultClip = -1,
178 Automatic = true,
179 Bullets = -1, -- -1 = disabled. Change for this variable to be returned by the accessor when bSecondary (SpecialActive by default) is true
180 Damage = -1,
181 Force = -1,
182 Range = -1,
183 Cooldown = -1,
184 WalkSpeed = -1,
185 RunSpeed = -1,
186 FireUnderwater = true,
187 FireInAir = true,
188 InterruptReload = false,
189 AutoReloadOnEmpty = false,
190 ReloadOnEmptyFire = false,
191 Spread = -1,
192 TracerFreq = -1,
193 TracerName = -1,
194 Delay = -1
195}
196
197SWEP.SingleReload = {
198 Enable = false, // True if this weapon reloads 1 round at a time (shotguns)
199 QueuedFire = true, -- Queue a primary/secondary fire to activate once the next bullet is put in the chamber
200 InitialRound = true -- Give round for the first reload (HL2) or subsequent reloads only (CS:S)
201}
202
203SWEP.Burst = {
204 Times = { -- Times between burst shots
205 0.1, -- Time until first extra shot
206 --0.1 -- Time until second extra shot; leave nil to fallback to the previous specified time
207 },
208 Count = 2, -- Number of extra shots to fire when burst is enabled
209 Cooldown = 0.3, -- Cooldown between toggling bursts
210 SingleActivity = false -- Only play the initial shooting activity during burst firing
211}
212
213SWEP.Zoom = {
214 FOV = { -- FOV for each zoom level
215 55 -- FOV for first zoom level
216 },
217 Times = { -- Times betweeen zoom levels
218 Fire = 0.1, -- Time to unzoom after firing
219 Rezoom = 0.05, -- Time to rezoom after unzooming from fire
220 Holster = 0, -- Time to unzoom after holstering
221 [0] = 0.15, -- Time to loop back to no zoom
222 0.2 -- Time to reach the first zoom level; leave nil to fallback to the zoom time in index 0
223 },
224 Levels = 1, -- Number of zoom levels
225 Cooldown = 0.3, -- Cooldown between zooming
226 UnzoomOnFire = false, -- Unzoom when the weapon is fired; rezooms after Primary/Secondary cooldown if the clip is not 0
227 UnzoomOnReload = true, -- Unzoom when the weapon is reloaded
228 HideViewModel = false, -- Hide view model when zoomed
229 FireDuringZoom = true, -- Allow fire during zoom/unzoom
230 PlayIdle = true -- Play idle animations in zoom
231}
232
233SWEP.IronSights = {
234 Times = {
235 1 -- Time it takes for the first viewmodel to zoom in
236 -- Second and third viewmodels fallback to first time
237 },
238 Pos = {
239 vector_origin -- Local position offset for ironsights to reach
240 },
241 Ang = {
242 angle_zero -- Local angular roation for ironsights to reach
243 },
244 Hold = false, -- Require secondary fire key to be held to use IronSights as opposed to just toggling the state
245 FireInZoom = true, -- Allow fire during zoom/unzoom
246 PlayIdle = false -- Play idle animations while enabled
247}
248
249SWEP.Grenade = {
250 Delay = -1, -- Set to -1 to disable any delay. 0 is 1 tick delay. The length of delay until the grenade is thrown
251 UpdateHoldType = true -- Update the holdtype to "normal" when out of grenades
252}
253
254SWEP.Melee = {
255 DotRange = 0.70721, -- Max dot product for a hull trace to hit. Not sure about this constant, but it's close to 1/sqrt(2)
256 HullRadius = 1.732, -- Test amount of the forward vector for the end point oof the hull trace. sqrt(3)
257 TestHull = Vector(16, 16, 16), -- Test hull mins/maxs
258 DamageType = DMG_CLUB, -- Melee damage type
259 Mask = MASK_SHOT_HULL, -- Mask to use for melee trace
260 StrictTrace = false -- Do not re-trace if there's a delay to the smack
261}
262
263SWEP.BipodDeploy = {
264 -- The names for the min/max pitch are mixed up in the engine
265 MinPitch = -60,
266 MaxPitch = 45,
267 MaxYaw = 45,
268 Delta = 5,
269 MaxHeightDifference = 20,
270 DownTrace = 16,
271 ForwardTrace = 32,
272 StepSize = 4,
273 TraceAttempts = 4,
274 BoxSize = 1,
275 MinDeployHeight = 24,
276 CheckTime = 0.2,
277 ForceUndeployPenalty = 0.1
278}
279
280SWEP.DoPump = false -- Do a pump animation after shooting
281SWEP.CheckClip1ForSecondary = false -- Check/remove ammo from Clip1 when secondary firing
282
283SWEP.AutoSwitchFrom = true -- Allows auto-switching away from the weapon. This is only checked for engine switching and is ignored by the base when AutoSwitchOnEmpty is true
284SWEP.AutoSwitchTo = true -- Allows auto-switching to this weapon
285SWEP.AutoSwitchOnEmpty = false -- Automatically switch away if the weapon is completely empty and the mouse is not being held. Ignores AutoSwitchFrom
286SWEP.SwitchOnEmptyFire = false -- Switch away if the weapon is fired with no ammo
287SWEP.RemoveOnEmpty = false -- Remove the weapon when it runs out of ammo
288SWEP.BlockDeployOnEmpty = false -- Block deploying the weapon if it has no ammo
289
290SWEP.UnderwaterCooldown = 0.2 -- Time between empty sound if the weapon cannot fire underwater. Set to -1 to only play once per mouse press
291SWEP.EmptyCooldown = -1 -- Time between empty sounds. Set to -1 to only play once per mouse press
292SWEP.HolsterReloadTime = -1 -- How long it should take for the weapon to reload if the player holsters during a reload. Set to -1 to cancel all reload activity on holster
293
294SWEP.MaxShots = 15 -- Number of shots to clamp the ShotsFired incrementer to when the mouse is lifted
295SWEP.ShotDecreaseTime = 0.0225 -- (CS:S crosshair) How fast the shot count should decrease per shot
296SWEP.ShotInitialDecreaseTime = 0.4 -- (CS:S crosshair) How long until the shot decrement starts after the mouse is lifter
297
298SWEP.TriggerBoundSize = 36 -- Trigger box size to pickup the weapon off the ground. Set to -1 to disable pickup. // Bloat the box for player pickup
299
300--SWEP.m_WeaponDeploySpeed = 1 -- Do NOT use this, the deploy animation will be cut short by the idle animation! Instead, set the "rate" key in the SWEP.Activities.deploy table
301
302local bSinglePlayer = game.SinglePlayer()
303local bRun = SERVER or not bSinglePlayer
304local ENTITY = FindMetaTable( "Entity" )
305local sFormatOne = "%s_%s"
306local sFormatTwo = "%s_%s_%s"
307
308--- Spawn/Constructor
309local sm_tPrecached = {} -- Persists through all weapon instances - acts like static keyword in C++
310
311function SWEP:Initialize()
312 local sClass = self:GetClass()
313 DevMsg( 2, sClass .. " (weapon_gs_base) Initialize" )
314
315 self.FireFunction = ENTITY.FireBullets -- Fire function to use with Shoot. Args are ( pPlayer, tFireBulletsInfo )
316 self.PunchDecayFunction = nil -- Function to decay the punch angle manually. Args are ( pPlayer, aPunchAngle ). nil goes to default decaying
317
318 self.m_bAutoSwitchFrom = self.AutoSwitchFrom
319 self.m_bInitialized = true
320 self.m_bHolsterAnim = false
321 self.m_bUpdateThrowHoldType = false
322 self.m_flDeployYawStart = 0
323 self.m_flDeployYawLeft = 0
324 self.m_flDeployYawRight = 0
325 self.m_sHoldType = self.HoldType
326 self.m_sWorldModel = self.WorldModel
327 self.m_tEvents = {}
328 self.m_tEventHoles = {}
329 self.m_tRemovalQueue = {}
330
331 if ( bRun ) then
332 self.m_bActiveNoAmmo = false
333 self.m_bDeployCrouched = false
334 self.m_bSwitchInvalid = false
335 self.m_flDeployHeight = 0
336 self.m_flDeployTime = 0
337 self.m_vDeployPos = vector_origin
338 self.m_pDeployEntity = NULL
339 self.m_pSwitchWeapon = NULL
340 self.m_tDryFire = { [0] = false, false, false }
341 --self.m_tSimulateHolsterAnim = { [0] = { false, 0, 0 }, { false, 0, 0 }, { false, 0, 0 }}
342 self.m_tUseViewModel = { [0] = false, false, false }
343 end
344
345 self:SetHoldType( self.m_sHoldType )
346
347 -- If it was created silenced, make it appear that way
348 self:UpdateVariables()
349
350 if ( self.TriggerBoundSize == -1 ) then
351 self:UseTriggerBounds( false, 0 )
352 else
353 self:UseTriggerBounds( true, self.TriggerBoundSize )
354 end
355
356 if ( CLIENT ) then
357 self.m_bActive = false
358
359 -- 0 = At rest, zoomed out
360 -- 1 = Zooming in
361 -- 2 = At rest, zoomed in
362 -- 3 = Zooming out
363 self.m_iIronSightsState0 = 0
364 self.m_iIronSightsState1 = 0
365 self.m_iIronSightsState2 = 0
366
367 -- For CS:S crosshair
368 self.m_iAmmoLastCheck = 0
369 self.m_flCrosshairDistance = 0
370 end
371
372 if ( not sm_tPrecached[sClass] ) then
373 self:Precache()
374 end
375end
376
377function SWEP:Precache()
378 local sClass = self:GetClass()
379 sm_tPrecached[sClass] = true
380
381 local tWeapon = weapons.GetStored( sClass )
382 DevMsg( 2, sClass .. " (weapon_gs_base) Precache" )
383
384 if ( CLIENT and self.KillIcon ~= '' ) then
385 -- Add KillIcon
386 killicon.AddFont( sClass, self.KillIconFont, self.KillIcon, self.KillIconColor )
387 end
388
389 -- Precache all weapon models
390 util.PrecacheModel( self.ViewModel )
391 util.PrecacheModel( self.ViewModel1 )
392 util.PrecacheModel( self.ViewModel2 )
393 util.PrecacheModel( self.m_sWorldModel )
394 util.PrecacheModel( self.SilencerModel )
395 util.PrecacheModel( self.DroppedModel )
396 util.PrecacheModel( self.ReloadModel )
397
398 -- Setup and precache all weapon sounds
399 for k, s in pairs( self.Sounds ) do
400 if ( k ~= "BaseClass" ) then -- Stupid pseudo-inheritance
401 -- Register sound table
402 if ( istable( s )) then
403 if ( not tWeapon.Sounds ) then
404 tWeapon.Sounds = {}
405 end
406
407 if ( s.sound ) then
408 if ( not s.name ) then
409 s.name = sClass .. "." .. k
410 end
411
412 if ( not s.channel ) then
413 s.channel = CHAN_WEAPON
414 end
415
416 sound.Add( s )
417 self.Sounds[k] = s.name
418 tWeapon.Sounds[k] = s.name
419 util.PrecacheSound( s.name )
420 else
421 local sName = s.name or sClass .. "." .. k
422 local bAdded = false
423 local tDefault
424
425 for i = 1, 3 do
426 local Sub = s[i]
427
428 if ( Sub ) then
429 bAdded = true
430
431 if ( istable( Sub )) then
432 local sNewName = sName .. i
433
434 if ( Sub.name ) then
435 -- Register the preset name, but the weapon won't use it
436 sound.Add( Sub )
437 end
438
439 Sub.name = sNewName
440 sound.Add( Sub )
441 util.PrecacheSound( sNewName )
442
443 if ( not tDefault ) then
444 tDefault = Sub
445 end
446 elseif ( string.IsSoundFile( Sub )) then
447 local sNewName = sName .. i
448 local tSound = {
449 name = sNewName,
450 channel = CHAN_WEAPON,
451 sound = Sub
452 }
453
454 if ( not tDefault ) then
455 tDefault = tSound
456 end
457
458 sound.Add( tSound )
459 util.PrecacheSound( sNewName )
460 else
461 local tNewSound = sound.GetProperties( Sub )
462
463 if ( tNewSound ) then
464 local sNewName = sName .. i
465 tNewSound.name = sNewName
466 sound.Add( tNewSound )
467 util.PrecacheSound( sNewName )
468
469 if ( not tDefault ) then
470 tDefault = tNewSound
471 end
472 elseif ( tDefault ) then
473 local sNewName = sName .. i
474 tDefault.name = sNewName
475 sound.Add( tDefault )
476 util.PrecacheSound( sNewName )
477 end
478 end
479 elseif ( tDefault ) then
480 tDefault.name = sName .. i
481 sound.Add( tDefault )
482 util.PrecacheSound( tDefault.name )
483 end
484 end
485
486 sName = bAdded and sName .. "%d" or ""
487 self.Sounds[k] = sName
488 tWeapon.Sounds[k] = sName
489 end
490 -- Create a new sound table from a file
491 elseif ( string.IsSoundFile( s )) then
492 local sName = sClass .. "." .. k
493
494 sound.Add({
495 name = sName,
496 channel = CHAN_WEAPON,
497 sound = s
498 })
499
500 util.PrecacheSound( sName )
501 self.Sounds[k] = sName
502
503 if ( not tWeapon.Sounds ) then
504 tWeapon.Sounds = {}
505 end
506
507 tWeapon.Sounds[k] = sName
508 -- Assume the sound table already exists
509 else
510 util.PrecacheSound( s )
511 end
512 end
513 end
514end
515
516function SWEP:SetupDataTables()
517 self:AddNWVar( "Bool", "Active", false ) -- To manage clientside deploying
518 self:AddNWVar( "Int", "Burst" )
519 self:AddNWVar( "Int", "Deployed" )
520 self:AddNWVar( "Int", "IronSights" )
521 self:AddNWVar( "Int", "ShotsFired" )
522 self:AddNWVar( "Int", "ShouldThrow" )
523 self:AddNWVar( "Int", "Silenced" )
524 self:AddNWVar( "Int", "ZoomLevel" )
525 self:AddNWVar( "Float", "LastShootTime" )
526 self:AddNWVar( "Float", "NextDeployCheck" )
527 self:AddNWVar( "Float", "NextIdle" )
528 self:AddNWVar( "Float", "NextIdle1" )
529 self:AddNWVar( "Float", "NextIdle2" )
530 self:AddNWVar( "Float", "NextItemFrame" )
531 self:AddNWVar( "Float", "NextReload" )
532 self:AddNWVar( "Float", "ReduceShotTime" )
533 self:AddNWVar( "Float", "ZoomActiveTime" )
534 self:AddNWVar( "Float", "ZoomActiveTime1" )
535 self:AddNWVar( "Float", "ZoomActiveTime2" )
536
537 -- Below are the default CNetworkVars in the engine for reference
538 --self:AddNWVar( "Entity", "Owner" )
539 --self:AddNWVar( "Float", "NextPrimaryAttack" )
540 --self:AddNWVar( "Float", "NextSecondaryAttack" )
541 --self:AddNWVar( "Int", "ViewModelIndex" )
542 --self:AddNWVar( "Int", "WorldModelIndex" )
543 --self:AddNWVar( "Int", "State" )
544 --self:AddNWVar( "Int", "PrimaryAmmoType" )
545 --self:AddNWVar( "Int", "SecondaryAmmoType" )
546 --self:AddNWVar( "Int", "Clip1" )
547 --self:AddNWVar( "Int", "Clip2" )
548end
549
550--- Deploy
551function SWEP:CanDeploy()
552 return true
553end
554
555function SWEP:Deploy()
556 local pPlayer = self:GetOwner()
557
558 // Dead men deploy no weapons
559 if ( pPlayer == NULL or not pPlayer:Alive() ) then
560 return false
561 end
562
563 if ( CLIENT and pPlayer.m_flDeployTime == CurTime() ) then
564 self:SharedDeploy( false )
565
566 return true
567 end
568
569 if ( not self:HasAmmo() ) then
570 if ( self.RemoveOnEmpty ) then
571 if ( SERVER ) then
572 self:Remove()
573 end
574
575 return false
576 end
577
578 if ( self.BlockDeployOnEmpty ) then
579 return false
580 end
581 end
582
583 -- Do not deploy again
584 if ( self.dt.Active and (SERVER or self.m_bActive) ) then
585 return true
586 end
587
588 if ( self:CanDeploy() ) then
589 self:SharedDeploy( false )
590
591 return true
592 end
593
594 DevMsg( 2, string.format( "%s (weapon_gs_base) Deploy invalid", self:GetClass() ) .. (IsFirstTimePredicted() and "" or " (predicted)") )
595
596 return false
597end
598
599function SWEP:SharedDeploy( bDelayed )
600 -- Clientside does not initialize sometimes
601 if ( self.m_bInitialized ) then
602 self:UpdateVariables()
603 else
604 self:Initialize()
605 end
606
607 DevMsg( 2, self:GetClass() .. " (weapon_gs_base) Deploy" .. ((bSinglePlayer or IsFirstTimePredicted()) and (bDelayed and " late" or "") or " (predicted)") )
608
609 local pPlayer = self:GetOwner()
610 local flCurTime = CurTime()
611
612 if ( CLIENT ) then
613 self.m_bActive = true
614 pPlayer.m_flDeployTime = flCurTime
615
616 -- In-case the client wasn't holstered
617 table.Empty( self.m_tEvents )
618 table.Empty( self.m_tEventHoles )
619 table.Empty( self.m_tRemovalQueue )
620 end
621
622 if ( bRun ) then
623 self.m_bActiveNoAmmo = not self:HasAmmo()
624
625 local iIndex = -self:GetShouldThrow() - 1
626
627 if ( not (iIndex == -1 or pPlayer:GetAmmoCount( self:GetGrenadeAmmoName() ) == 0) ) then
628 iIndex = -1
629
630 if ( not bDelayed ) then
631 self:SetShouldThrow(0)
632 end
633 end
634
635 local bPlayed = false
636 local pHideModel
637
638 -- Wait for all viewmodels to deploy
639 local flSequenceDuration = 0
640
641 if ( self.ViewModel == "" ) then
642 self.m_tUseViewModel[0] = false
643 self.m_tDryFire[0] = false
644 else
645 local pViewModel = pPlayer:GetViewModel(0)
646
647 if ( pViewModel == NULL ) then
648 self.m_tUseViewModel[0] = false
649 self.m_tDryFire[0] = false
650 else
651 self.m_tUseViewModel[0] = true
652 pViewModel:SetSkin( self:GetViewModelSkin(0) )
653
654 if ( not bDelayed ) then
655 if ( iIndex == 0 ) then
656 pHideModel = pViewModel
657 else
658 self:PlaySound( "draw", 0 )
659 bPlayed = true
660
661 if ( self:PlayActivity( "draw", 0 )) then
662 flSequenceDuration = self:SequenceLength(0)
663 end
664 end
665 end
666
667 local iActivity = self:LookupActivity( self:GetDryfireActivity(0), 0 )
668 self.m_tDryFire[0] = not (iActivity == ACT_INVALID or pViewModel:SelectWeightedSequence( iActivity ) == -1)
669 end
670 end
671
672 if ( self.ViewModel1 == "" ) then
673 self.m_tUseViewModel[1] = false
674 self.m_tDryFire[1] = false
675 else
676 local pViewModel = pPlayer:GetViewModel(1)
677
678 if ( pViewModel == NULL ) then
679 self.m_tUseViewModel[1] = false
680 self.m_tDryFire[1] = false
681 else
682 self.m_tUseViewModel[1] = true
683 pViewModel:SetSkin( self:GetViewModelSkin(1) )
684
685 if ( not bDelayed ) then
686 pViewModel:SetWeaponModel( self.ViewModel1, self )
687
688 if ( iIndex == 1 ) then
689 pHideModel = pViewModel
690 else
691 if ( not (self:PlaySound( "draw", 1, true ) or bPlayed) ) then
692 self:PlaySound( "draw", 0 )
693 bPlayed = true
694 end
695
696 if ( self:PlayActivity( "draw", 1 )) then
697 flSequenceDuration = math.max( flSequenceDuration, self:SequenceLength(1) )
698 end
699 end
700 end
701
702 local iActivity = self:LookupActivity( self:GetDryfireActivity(1), 1 )
703 self.m_tDryFire[1] = not (iActivity == ACT_INVALID or pViewModel:SelectWeightedSequence( iActivity ) == -1)
704 end
705 end
706
707 if ( self.ViewModel2 == "" ) then
708 self.m_tUseViewModel[2] = false
709 self.m_tDryFire[2] = false
710 else
711 local pViewModel = pPlayer:GetViewModel(2)
712
713 if ( pViewModel == NULL ) then
714 self.m_tUseViewModel[2] = false
715 self.m_tDryFire[2] = false
716 else
717 self.m_tUseViewModel[2] = true
718 pViewModel:SetSkin( self:GetViewModelSkin(2) )
719
720 if ( not bDelayed ) then
721 pViewModel:SetWeaponModel( self.ViewModel2, self )
722
723 if ( iIndex == 2 ) then
724 pHideModel = pViewModel
725 else
726 if ( not (self:PlaySound( "draw", 2, true ) or bPlayed) ) then
727 self:PlaySound( "draw", 0 )
728 bPlayed = true
729 end
730
731 if ( self:PlayActivity( "draw", 2 )) then
732 flSequenceDuration = math.max( flSequenceDuration, self:SequenceLength(2) )
733 end
734 end
735 end
736
737 local iActivity = self:LookupActivity( self:GetDryfireActivity(2), 2 )
738 self.m_tDryFire[2] = not (iActivity == ACT_INVALID or pViewModel:SelectWeightedSequence( iActivity ) == -1)
739 end
740 end
741
742 if ( iIndex ~= -1 ) then
743 pHideModel:SetVisible( false )
744
745 self:AddEvent( "reload_grenade", 0, function()
746 if ( pPlayer:GetAmmoCount( self:GetGrenadeAmmoName() ) ~= 0 ) then
747 self:SetShouldThrow(0)
748 local flNewTime
749
750 if ( pHideModel == NULL ) then
751 flNewTime = CurTime()
752 else
753 flNewTime = CurTime() + (self:PlayActivity( "draw", iIndex ) and self:SequenceLength( iIndex ) or 0)
754 pHideModel:SetVisible( true )
755 self:PlaySound( "draw", iIndex )
756 end
757
758 self:SetNextPrimaryFire( flNewTime )
759 self:SetNextSecondaryFire( flNewTime )
760 self:SetNextReload( flNewTime )
761
762 return true
763 end
764 end )
765 end
766
767 if ( not bDelayed ) then
768 self.dt.Active = true
769 self:SetZoomLevel(0)
770 self:SetDeployed(0)
771 self:SetReduceShotTime(0)
772 self:SetShotsFired(0)
773
774 pPlayer:SetFOV(0, 0)
775
776 // Can't shoot again until we've finished deploying
777 flSequenceDuration = flSequenceDuration + flCurTime
778 self:SetNextPrimaryFire( flSequenceDuration )
779 self:SetNextSecondaryFire( flSequenceDuration )
780 self:SetNextReload( flSequenceDuration )
781 end
782 end
783end
784
785--- Holster/Remove
786function SWEP:CanHolster()
787 return true
788end
789
790function SWEP:Holster( pSwitchingTo )
791 local pPlayer = self:GetOwner()
792 local bIsValid = pPlayer ~= NULL
793 local flCurTime = CurTime()
794
795 if ( CLIENT and bIsValid ) then
796 if ( pPlayer.m_flDeployTime == flCurTime ) then
797 if ( pSwitchingTo ~= self ) then
798 self:SharedHolster( pSwitchingTo, false )
799 end
800
801 return true
802 end
803
804 if ( pPlayer.m_flHolsterAnimTime == flCurTime ) then
805 -- Stupid Garry manual holster
806 if ( pSwitchingTo ~= self ) then
807 self:HolsterAnim( pSwitchingTo, false )
808 end
809
810 return false
811 end
812 end
813
814 if ( self.RemoveOnEmpty and not self:HasAmmo() ) then
815 if ( SERVER ) then
816 self:Remove()
817 end
818
819 return true
820 end
821
822 -- Do not holster again
823 -- https://github.com/Facepunch/garrysmod-issues/issues/2854
824 if ( SERVER and not self.dt.Active or CLIENT and not self.m_bActive ) then
825 return true
826 end
827
828 if ( bIsValid ) then
829 -- Holster is called when the player dies with it active but nothing should be done
830 -- Player:Alive() will return true on the frame the death occured but the health will be less than or equal to 0
831 if ( pPlayer:Health() < 1 or not pPlayer:Alive() ) then
832 return true
833 end
834 end
835
836 if ( self:GetDeployed() ~= 0 ) then
837 return false
838 end
839
840 -- If we are switching to NULL (picked up an object) don't play an animation
841 if ( bIsValid and self.HolsterAnimation and pSwitchingTo ~= NULL ) then
842 if ( not self.m_bHolsterAnim ) then
843 if ( pSwitchingTo ~= self and self:CanHolster( pSwitchingTo )) then
844 self:HolsterAnim( pSwitchingTo, false )
845
846 if ( SERVER ) then
847 -- Run this clientside to reset the viewmodels and set the variables for a full holster
848 if ( bSinglePlayer ) then
849 net.Start( "GSWeapons-Holster animation" )
850 net.WriteEntity( self )
851 net.WriteEntity( pSwitchingTo )
852 net.Send( pPlayer )
853 else
854 -- Give the client a chance to holster themselves
855 timer.Create( "GSWeapons-Holster animation-" .. self:EntIndex(), 0, 1, function()
856 if ( not (self == NULL or pPlayer == NULL) and self:GetOwner() == pPlayer ) then
857 net.Start( "GSWeapons-Holster animation" )
858 net.WriteEntity( self )
859 net.WriteEntity( pSwitchingTo )
860 net.Send( pPlayer )
861 end
862 end )
863 end
864 end
865 end
866
867 return false
868 end
869
870 if ( self:GetNextItemFrame() > flCurTime ) then
871 return false
872 end
873
874 if ( pSwitchingTo ~= self ) then
875 self:SharedHolster( pSwitchingTo, false )
876
877 if ( SERVER ) then
878 -- Clientside does not run Holster in single-player
879 if ( bSinglePlayer ) then
880 net.Start( "GSWeapons-Holster" )
881 net.WriteEntity( self )
882 net.WriteEntity( pSwitchingTo )
883 net.Send( pPlayer )
884 else
885 timer.Create( "GSWeapons-Holster-" .. self:EntIndex(), 0, 1, function()
886 if ( not (self == NULL or pPlayer == NULL) and self:GetOwner() == pPlayer ) then
887 net.Start( "GSWeapons-Holster" )
888 net.WriteEntity( self )
889 net.WriteEntity( pSwitchingTo )
890 net.Send( pPlayer )
891 end
892 end )
893 end
894 end
895 end
896
897 return true
898 end
899
900 if ( self:CanHolster( pSwitchingTo )) then
901 if ( pSwitchingTo ~= self ) then
902 self:SharedHolster( pSwitchingTo, false )
903 end
904
905 return true
906 end
907
908 DevMsg( 2, string.format( "%s (weapon_gs_base) Holster invalid to %s", self:GetClass(), tostring( pSwitchingTo )) .. (IsFirstTimePredicted() and "" or " (predicted)") )
909
910 return false
911end
912
913function SWEP:HolsterAnim( pSwitchingTo, bDelayed )
914 DevMsg( 2, string.format( "%s (weapon_gs_base) Holster animation to %s", self:GetClass(), tostring( pSwitchingTo )) .. (bDelayed and " late" or IsFirstTimePredicted() and "" or " (predicted)") )
915
916 self.m_bHolsterAnim = true
917 local pPlayer = self:GetOwner()
918
919 if ( not bDelayed and CLIENT ) then
920 pPlayer.m_flHolsterAnimTime = CurTime()
921 end
922
923 if ( bRun ) then
924 if ( IsFirstTimePredicted() ) then
925 self.m_bSwitchInvalid = pSwitchingTo == NULL
926 self.m_pSwitchWeapon = pSwitchingTo
927
928 if ( self.HolsterReloadTime ~= -1 and self:EventActive( "reload" )) then
929 -- If self is NULL in the hook, there's no way to retrieve what EntIndex it had
930 local sName = "GSWeapons-Holster reload-" .. self:EntIndex()
931 local flReloadTime
932 local bDidReload = false
933
934 hook.Add( "Think", sName, function()
935 if ( bDidReload and IsFirstTimePredicted() ) then
936 hook.Remove( "Think", sName )
937 elseif ( self == NULL or pPlayer == NULL ) then
938 hook.Remove( "Think", sName )
939 elseif ( not self:IsActiveWeapon() ) then
940 if ( flReloadTime ) then
941 if ( CurTime() >= flReloadTime ) then
942 local iMaxClip = self:GetMaxClip1()
943
944 if ( iMaxClip ~= -1 ) then
945 local iClip = self:Clip1()
946 local sAmmoType = self:GetPrimaryAmmoName()
947 local iAmmo = math.min( iMaxClip - iClip, pPlayer:GetAmmoCount( sAmmoType ))
948 self:SetClip1( iClip + iAmmo )
949 pPlayer:RemoveAmmo( iAmmo, sAmmoType )
950 end
951
952 iMaxClip = self:GetMaxClip2()
953
954 // If I use secondary clips, reload secondary
955 if ( iMaxClip ~= -1 ) then
956 local iClip = self:Clip2()
957 local sAmmoType = self:GetSecondaryAmmoName()
958 local iAmmo = math.min( iMaxClip - iClip, pPlayer:GetAmmoCount( sAmmoType ))
959 self:SetClip2( iClip + iAmmo )
960 pPlayer:RemoveAmmo( iAmmo, sAmmoType )
961 end
962
963 self:FinishReload()
964 bDidReload = true
965 end
966 else
967 flReloadTime = CurTime() + self.HolsterReloadTime
968 end
969 end
970 end )
971 end
972 end
973
974 if ( not bDelayed ) then
975 local iIndex = -self:GetShouldThrow() - 1
976 local bPlayed = false
977
978 -- Wait for all viewmodels to holster
979 local flEndTime = 0
980 local flCurTime = CurTime()
981
982 if ( self.m_tUseViewModel[0] and iIndex ~= 0 ) then
983 self:PlaySound( "holster", 0 )
984 bPlayed = true
985
986 if ( self:PlayActivity( "holster", 0 )) then
987 flEndTime = self:SequenceLength(0)
988 --[[elseif ( self:PlayActivity( "draw", 0 )) then
989 flEndTime = self:SequenceLength(0)
990
991 local tbl = self.m_tSimulateHolsterAnim[0]
992 tbl[1] = true
993 tbl[2] = flCurTime
994 tbl[3] = flEndTime
995
996 -- Guaranteed to be valid
997 pPlayer:GetViewModel(0):SetCycle(1)]]
998 end
999 end
1000
1001 if ( self.m_tUseViewModel[1] and iIndex ~= 1 ) then
1002 if ( not (self:PlaySound( "holster", 1, true ) or bPlayed) ) then
1003 self:PlaySound( "holster", 0 )
1004 bPlayed = true
1005 end
1006
1007 if ( self:PlayActivity( "holster", 1 )) then
1008 flEndTime = math.max( flEndTime, self:SequenceLength(1) )
1009 --[[elseif ( self:PlayActivity( "draw", 1 )) then
1010 local flSeqDur = self:SequenceLength(1)
1011 flEndTime = math.max( flEndTime, flSeqDur )
1012
1013 local tbl = self.m_tSimulateHolsterAnim[1]
1014 tbl[1] = true
1015 tbl[2] = flCurTime
1016 tbl[3] = flSeqDur
1017
1018 pPlayer:GetViewModel(1):SetCycle(1)]]
1019 end
1020 end
1021
1022 if ( self.m_tUseViewModel[2] and iIndex ~= 2 ) then
1023 if ( not (self:PlaySound( "holster", 2, true ) or bPlayed) ) then
1024 self:PlaySound( "holster", 0 )
1025 bPlayed = true
1026 end
1027
1028 if ( self:PlayActivity( "holster", 2 )) then
1029 flEndTime = math.max( flEndTime, self:SequenceLength(2) )
1030 --[[elseif ( self:PlayActivity( "draw", 2 )) then
1031 local flSeqDur = self:SequenceLength(2)
1032 flEndTime = math.max( flEndTime, flSeqDur )
1033
1034 local tbl = self.m_tSimulateHolsterAnim[2]
1035 tbl[1] = true
1036 tbl[2] = flCurTime
1037 tbl[3] = flSeqDur
1038
1039 pPlayer:GetViewModel(2):SetCycle(1)]]
1040 end
1041 end
1042
1043 self:SetZoomLevel(0)
1044 self:SetDeployed(0)
1045
1046 -- Disable all events during Holster animation
1047 self:SetNextPrimaryFire(-1)
1048 self:SetNextSecondaryFire(-1)
1049 self:SetNextReload(-1)
1050 self:SetNextItemFrame( flCurTime + flEndTime )
1051 end
1052 end
1053
1054 -- https://github.com/Facepunch/garrysmod-requests/issues/739
1055 table.Empty( self.m_tEvents )
1056 table.Empty( self.m_tEventHoles )
1057 table.Empty( self.m_tRemovalQueue )
1058end
1059
1060function SWEP:SharedHolster( pSwitchingTo, bDelayed )
1061 DevMsg( 2, string.format( "%s (weapon_gs_base) Holster to %s", self:GetClass(), tostring( pSwitchingTo )) .. (bDelayed and " late" or IsFirstTimePredicted() and "" or " (predicted)") )
1062
1063 local pPlayer = self:GetOwner()
1064 local bIsValid = pPlayer ~= NULL
1065 local bNoAnim = not self.HolsterAnimation
1066 self.m_bHolsterAnim = false
1067
1068 if ( CLIENT ) then
1069 self.m_bActive = false
1070
1071 if ( bIsValid and not bDelayed ) then
1072 pPlayer.m_flDeployTime = CurTime()
1073 end
1074 end
1075
1076 if ( bRun ) then
1077 --local tbl = self.m_tSimulateHolsterAnim
1078 --tbl[0][1] = false
1079 --tbl[1][1] = false
1080 --tbl[2][1] = false
1081
1082 if ( bNoAnim ) then
1083 self:SetZoomLevel(0)
1084 self:SetDeployed(0)
1085
1086 -- Disable all actions during holster
1087 self:SetNextPrimaryFire(-1)
1088 self:SetNextSecondaryFire(-1)
1089 self:SetNextReload(-1)
1090 -- FIXME: Change this to a variable
1091 if ( bIsValid and self.HolsterReloadTime ~= -1 and self:EventActive( "reload" )) then
1092 -- If self is NULL in the hook, there's no way to retrieve what EntIndex it had
1093 local sName = "GSWeapons-Holster reload-" .. self:EntIndex()
1094 local flReloadTime
1095 local bDidReload = false
1096
1097 hook.Add( "Think", sName, function()
1098 if ( bDidReload and IsFirstTimePredicted() ) then
1099 hook.Remove( "Think", sName )
1100 elseif ( self == NULL or pPlayer == NULL ) then
1101 hook.Remove( "Think", sName )
1102 elseif ( not self:IsActiveWeapon() ) then
1103 if ( flReloadTime ) then
1104 if ( CurTime() >= flReloadTime ) then
1105 local iMaxClip = self:GetMaxClip1()
1106
1107 if ( iMaxClip ~= -1 ) then
1108 local iClip = self:Clip1()
1109 local sAmmoType = self:GetPrimaryAmmoName()
1110 local iAmmo = math.min( iMaxClip - iClip, pPlayer:GetAmmoCount( sAmmoType ))
1111 self:SetClip1( iClip + iAmmo )
1112 pPlayer:RemoveAmmo( iAmmo, sAmmoType )
1113 end
1114
1115 iMaxClip = self:GetMaxClip2()
1116
1117 // If I use secondary clips, reload secondary
1118 if ( iMaxClip ~= -1 ) then
1119 local iClip = self:Clip2()
1120 local sAmmoType = self:GetSecondaryAmmoName()
1121 local iAmmo = math.min( iMaxClip - iClip, pPlayer:GetAmmoCount( sAmmoType ))
1122 self:SetClip2( iClip + iAmmo )
1123 pPlayer:RemoveAmmo( iAmmo, sAmmoType )
1124 end
1125
1126 self:FinishReload()
1127 bDidReload = true
1128 end
1129 else
1130 flReloadTime = CurTime() + self.HolsterReloadTime
1131 end
1132 end
1133 end )
1134 end
1135 end
1136
1137 if ( not bDelayed ) then
1138 self.dt.Active = false
1139 self:SetNextIdle(-1)
1140 self:SetNextIdle1(-1)
1141 self:SetNextIdle2(-1)
1142
1143 if ( bIsValid ) then
1144 pPlayer:SetFOV( 0, self.Zoom.Times.Holster ) // reset the default FOV
1145 local iIndex = -self:GetShouldThrow() - 1
1146 local bPlayed = false
1147
1148 if ( bNoAnim and iIndex ~= 0 ) then
1149 self:PlaySound( "holster", 0 )
1150 bPlayed = true
1151 end
1152
1153 if ( self.m_tUseViewModel[1] ) then
1154 if ( bNoAnim and not (iIndex == 1 or self:PlaySound( "holster", 1, true ) or bPlayed) ) then
1155 self:PlaySound( "holster", 0 )
1156 bPlayed = true
1157 end
1158
1159 local pViewModel = pPlayer:GetViewModel(1)
1160
1161 if ( pViewModel ~= NULL ) then
1162 pViewModel:SetWeaponModel("")
1163 end
1164 end
1165
1166 if ( self.m_tUseViewModel[2] ) then
1167 if ( bNoAnim and not (iIndex == 2 or self:PlaySound( "holster", 2, true ) or bPlayed) ) then
1168 self:PlaySound( "holster", 0 )
1169 bPlayed = true
1170 end
1171
1172 local pViewModel = pPlayer:GetViewModel(2)
1173
1174 if ( pViewModel ~= NULL ) then
1175 pViewModel:SetWeaponModel("")
1176 end
1177 end
1178 end
1179 end
1180 end
1181
1182 -- These are already set if there was a holster animation
1183 if ( bNoAnim ) then
1184 -- https://github.com/Facepunch/garrysmod-requests/issues/739
1185 table.Empty( self.m_tEvents )
1186 table.Empty( self.m_tEventHoles )
1187 table.Empty( self.m_tRemovalQueue )
1188 end
1189end
1190
1191function SWEP:OnRemove()
1192 local pPlayer = self:GetOwner()
1193
1194 if ( pPlayer == NULL ) then
1195 DevMsg( 2, self:GetClass() .. " (weapon_gs_base) Remove invalid" )
1196 else
1197 if ( bRun ) then
1198 pPlayer:SetFOV( 0, self.Zoom.Times.Holster ) // reset the default FOV
1199
1200 -- Hide the extra viewmodels
1201 if ( self.m_tUseViewModel[1] ) then
1202 local pViewModel = pPlayer:GetViewModel(1)
1203
1204 if ( pViewModel ~= NULL ) then
1205 pViewModel:SetWeaponModel("")
1206 end
1207 end
1208
1209 if ( self.m_tUseViewModel[2] ) then
1210 local pViewModel = pPlayer:GetViewModel(2)
1211
1212 if ( pViewModel ~= NULL ) then
1213 pViewModel:SetWeaponModel("")
1214 end
1215 end
1216 end
1217
1218 if ( pPlayer:Health() > 0 and pPlayer:Alive() and self:IsActiveWeapon() ) then
1219 -- The weapon was removed while it was active and the player was alive, so find a new one
1220 local pWeapon = pPlayer:GetNextBestWeapon( self.HighWeightPriority, true )
1221
1222 if ( bRun ) then
1223 pPlayer:SwitchWeapon( pWeapon )
1224 end
1225
1226 DevMsg( 2, string.format( "%s (weapon_gs_base) Remove to %s", self:GetClass(), tostring( pWeapon )))
1227 else
1228 DevMsg( 2, self:GetClass() .. " (weapon_gs_base) Remove invalid" )
1229 end
1230 end
1231end
1232
1233--- Think/Idle
1234function SWEP:CanIdle( iIndex --[[= 0]] )
1235 if ( not self.Zoom.PlayIdle and self:GetZoomLevel() > 0 ) then
1236 return false
1237 end
1238
1239 if ( not self.IronSights.PlayIdle and self:IronSightsEnabled( iIndex or 0 )) then
1240 return false
1241 end
1242
1243 local flNextIdle
1244
1245 if ( iIndex == 1 ) then
1246 if ( not self.ShouldIdle1 or self.ViewModel1 == "" ) then
1247 return false
1248 end
1249
1250 flNextIdle = self:GetNextIdle1()
1251 elseif ( iIndex == 2 ) then
1252 if ( not self.ShouldIdle2 or self.ViewModel2 == "" ) then
1253 return false
1254 end
1255
1256 flNextIdle = self:GetNextIdle2()
1257 else
1258 if ( not self.ShouldIdle or self.ViewModel == "" ) then
1259 return false
1260 end
1261
1262 flNextIdle = self:GetNextIdle()
1263 end
1264
1265 return flNextIdle ~= -1 and flNextIdle <= CurTime()
1266end
1267
1268function SWEP:Think()
1269 self:UpdateVariables()
1270 local pPlayer = self:GetOwner()
1271
1272 if ( pPlayer == NULL ) then
1273 return
1274 end
1275
1276 if ( self.m_bHolsterAnim ) then
1277 if ( bRun ) then
1278 --[[local flCurTime = CurTime()
1279 local tbl = self.m_tSimulateHolsterAnim[0]
1280
1281 if ( tbl[1] ) then
1282 local pViewModel = pPlayer:GetViewModel(0)
1283
1284 if ( pViewModel ~= NULL ) then
1285 pViewModel:SetCycle( math.max( 0, 1 - (flCurTime - tbl[2]) / tbl[3] ))
1286 end
1287 end
1288
1289 tbl = self.m_tSimulateHolsterAnim[1]
1290
1291 if ( tbl[1] ) then
1292 local pViewModel = pPlayer:GetViewModel(1)
1293
1294 if ( pViewModel ~= NULL ) then
1295 pViewModel:SetCycle( math.max( 0, 1 - (flCurTime - tbl[2]) / tbl[3] ))
1296 end
1297 end
1298
1299 tbl = self.m_tSimulateHolsterAnim[2]
1300
1301 if ( tbl[1] ) then
1302 local pViewModel = pPlayer:GetViewModel(2)
1303
1304 if ( pViewModel ~= NULL ) then
1305 pViewModel:SetCycle( math.max( 0, 1 - (flCurTime - tbl[2]) / tbl[3] ))
1306 end
1307 end]]
1308
1309 if ( self:GetNextItemFrame() <= CurTime() ) then
1310 if ( self.m_bSwitchInvalid ) then -- Switching to NULL to begin with
1311 pPlayer:SwitchWeapon( NULL )
1312 elseif ( self.m_pSwitchWeapon == NULL ) then -- Weapon disappeared; find a new one or come back to the same weapon
1313 pPlayer:SwitchWeapon( pPlayer:GetNextBestWeapon( self.HighWeightPriority ))
1314 else -- Weapon being swapped to is still on the player
1315 pPlayer:SwitchWeapon( self.m_pSwitchWeapon )
1316 end
1317 end
1318 end
1319
1320 return
1321 end
1322
1323 if ( CLIENT and (not self.m_bActive or not bSinglePlayer and pPlayer.m_flDeployTime == CurTime()) and self.dt.Active ) then
1324 -- For clientside deployment in single-player or by use of Player:SelectWeapon()
1325 self:SharedDeploy( true )
1326
1327 return
1328 end
1329
1330 local bFirstTimePredicted = IsFirstTimePredicted()
1331
1332 -- Events are removed one Think after they mark themselves as complete to maintain clientside prediction
1333 if ( bFirstTimePredicted ) then
1334 for key, _ in pairs( self.m_tRemovalQueue ) do
1335 self.m_tRemovalQueue[key] = nil
1336 self.m_tEvents[key] = nil
1337
1338 if ( isnumber( key )) then
1339 self.m_tEventHoles[key] = true
1340 end
1341 end
1342 end
1343
1344 -- Events have priority over main think function
1345 local flCurTime = CurTime()
1346
1347 for key, tbl in pairs( self.m_tEvents ) do
1348 -- Only start running on the first prediction time
1349 if ( bFirstTimePredicted ) then
1350 self.m_tEvents[key][4] = true
1351 elseif ( not self.m_tEvents[key][4] ) then
1352 continue
1353 end
1354
1355 -- -1 is an event that counts as active but never ran
1356 if ( tbl[1] ~= -1 ) then
1357 if ( tbl[2] <= flCurTime ) then
1358 local RetVal = tbl[3]()
1359
1360 if ( RetVal == true ) then
1361 self.m_tRemovalQueue[key] = true
1362 else
1363 -- Update interval
1364 if ( isnumber( RetVal )) then
1365 tbl[1] = RetVal
1366 end
1367
1368 tbl[2] = flCurTime + tbl[1]
1369 end
1370 end
1371 end
1372 end
1373
1374 if ( not (pPlayer:KeyDown( IN_ATTACK ) or pPlayer:KeyDown( IN_ATTACK2 )) ) then
1375 self:MouseLifted()
1376 end
1377
1378 local flNextThink = self:GetNextItemFrame()
1379
1380 if ( not (flNextThink == -1 or flNextThink > flCurTime) ) then
1381 self:ItemFrame()
1382 end
1383
1384 if ( bRun ) then
1385 local iIndex = self:GetDeployed() - 1
1386
1387 if ( iIndex ~= -1 ) then
1388 local flNextCheck = self:GetNextDeployCheck()
1389
1390 if ( not (flNextCheck == -1 or flNextCheck > flCurTime) ) then
1391 if ( self:ShouldUndeploy() and self:ToggleDeploy( iIndex )) then
1392 self:RemoveEvent( "reload" )
1393 self:SetNextReload(0)
1394
1395 if ( bSinglePlayer ) then
1396 net.Start( "GSWeapons-Finish reload" )
1397 net.Send( pPlayer )
1398 end
1399
1400 local flEndTime = flCurTime + self.BipodDeploy.ForceUndeployPenalty
1401 self:SetNextPrimaryFire( flEndTime )
1402 self:SetNextSecondaryFire( flEndTime )
1403 self:SetNextReload( flEndTime )
1404 end
1405
1406 self:SetNextDeployCheck( flCurTime + self.BipodDeploy.CheckTime )
1407 end
1408 end
1409
1410 if ( self:CanIdle(0) ) then
1411 self:PlayActivity( "idle", 0, nil, true, self.m_tDryFire[0] and self:GetActivitySuffix(0) == "empty" )
1412 end
1413
1414 if ( self:CanIdle(1) ) then
1415 self:PlayActivity( "idle", 1, nil, true, self.m_tDryFire[1] and self:GetActivitySuffix(1) == "empty" )
1416 end
1417
1418 if ( self:CanIdle(2) ) then
1419 self:PlayActivity( "idle", 2, nil, true, self.m_tDryFire[2] and self:GetActivitySuffix(2) == "empty" )
1420 end
1421 end
1422end
1423
1424function SWEP:UpdateVariables()
1425 if ( CLIENT and not self.dt.Active ) then
1426 self.m_bActive = false
1427 end
1428
1429 self.WorldModel = self.m_sWorldModel
1430 local iWorldIndex = self:GetWorldModelIndex()
1431
1432 -- Silencer has first priority
1433 if ( self.SilencerModel ~= "" and self:Silenced( iWorldIndex )) then
1434 self.WorldModel = self.SilencerModel
1435 elseif ( self.DroppedModel ~= "" and self:GetOwner() == NULL ) then
1436 self.WorldModel = self.DroppedModel
1437 elseif ( self.ReloadModel ~= "" and self:EventActive( "reload" )) then
1438 self.WorldModel = self.ReloadModel
1439 end
1440
1441 local iThrow = self:GetShouldThrow()
1442
1443 if ( iThrow > 0 ) then
1444 self.AutoSwitchFrom = false
1445
1446 if ( self.m_bUpdateThrowHoldType and self.Grenade.UpdateHoldType ) then
1447 self.m_bUpdateThrowHoldType = false
1448 self:SetHoldType( self.m_sHoldType )
1449 end
1450 elseif ( iThrow ~= 0 ) then
1451 if ( iThrow == -(iWorldIndex + 1) ) then
1452 self.WorldModel = ""
1453 end
1454
1455 self.AutoSwitchFrom = self.m_bAutoSwitchFrom
1456
1457 if ( self.Grenade.UpdateHoldType and self:GetHoldType() ~= "normal" ) then
1458 self.m_bUpdateThrowHoldType = true
1459 self:SetHoldType( "normal" )
1460 end
1461 else
1462 self.AutoSwitchFrom = self.m_bAutoSwitchFrom
1463
1464 if ( self.m_bUpdateThrowHoldType and self.Grenade.UpdateHoldType ) then
1465 self.m_bUpdateThrowHoldType = false
1466 self:SetHoldType( self.m_sHoldType )
1467 end
1468 end
1469end
1470
1471function SWEP:MouseLifted()
1472 if ( not bRun ) then
1473 return
1474 end
1475
1476 local pPlayer = self:GetOwner()
1477 local flCurTime = CurTime()
1478 local iThrow = self:GetShouldThrow()
1479
1480 if ( iThrow > 0 ) then
1481 self:SetShouldThrow(0)
1482 local iIndex = math.floor( iThrow / GRENADE_COUNT )
1483
1484 pPlayer:SetAnimation( PLAYER_ATTACK1 )
1485
1486 local flDelay = self.Grenade.Delay
1487
1488 if ( flDelay == -1 ) then
1489 self:SetLastShootTime( flCurTime )
1490 self:EmitGrenade( iThrow % GRENADE_COUNT )
1491 self:PlaySound( "throw", iIndex )
1492 pPlayer:RemoveAmmo( 1, self:GetGrenadeAmmoName() )
1493 else
1494 self:AddEvent( "throw", flDelay, function()
1495 self:SetLastShootTime( CurTime() )
1496 self:EmitGrenade( iThrow % GRENADE_COUNT )
1497 self:PlaySound( "throw", iIndex )
1498 pPlayer:RemoveAmmo( 1, self:GetGrenadeAmmoName() )
1499
1500 return true
1501 end )
1502 end
1503
1504 local flTime = 0
1505
1506 if ( self:PlayActivity( "throw", iIndex )) then
1507 flTime = self:SequenceLength( iIndex )
1508
1509 if ( flTime < flDelay ) then
1510 flTime = flDelay
1511 end
1512 end
1513
1514 self:AddEvent( "reload_grenade", flTime, function()
1515 -- This must be run AFTER throw
1516 if ( self:EventActive( "throw" )) then
1517 return 0
1518 end
1519
1520 if ( pPlayer:GetAmmoCount( self:GetGrenadeAmmoName() ) == 0 ) then
1521 if ( self.RemoveOnEmpty ) then
1522 if ( SERVER ) then
1523 self:Remove()
1524 end
1525 else
1526 local pViewModel = pPlayer:GetViewModel( iIndex )
1527 local bIsValid = pViewModel ~= NULL
1528
1529 if ( bIsValid ) then
1530 pViewModel:SetVisible( false )
1531 end
1532
1533 self:SetShouldThrow( -iIndex - 1 )
1534
1535 self:AddEvent( "draw", 0, function()
1536 if ( pPlayer:GetAmmoCount( self:GetGrenadeAmmoName() ) ~= 0 ) then
1537 if ( bIsValid ) then
1538 pViewModel:SetVisible( true )
1539 end
1540
1541 self:SetShouldThrow(0)
1542
1543 self:PlaySound( "draw", iIndex )
1544 local flNewTime = CurTime() + (self:PlayActivity( "draw", iIndex ) and self:SequenceLength( iIndex ) or 0)
1545 self:SetNextPrimaryFire( flNewTime )
1546 self:SetNextSecondaryFire( flNewTime )
1547 self:SetNextReload( flNewTime )
1548
1549 return true
1550 end
1551 end )
1552 end
1553 else
1554 local flNewTime = CurTime() + (self:PlayActivity( "draw", iIndex ) and self:SequenceLength( iIndex ) or 0)
1555 self:SetNextPrimaryFire( flNewTime )
1556 self:SetNextSecondaryFire( flNewTime )
1557 self:SetNextReload( flNewTime )
1558 end
1559
1560 return true
1561 end )
1562 end
1563
1564 -- Just ran out of ammo and the mouse has been lifted, so switch away
1565 if ( self.AutoSwitchOnEmpty and not self.m_bActiveNoAmmo and not self:HasAmmo() ) then
1566 pPlayer:SwitchWeapon( pPlayer:GetNextBestWeapon( self.HighWeightPriority ))
1567 -- Reload is still called serverside only in single-player
1568 elseif ( self:Clip1() == 0 and self.Primary.AutoReloadOnEmpty or self:Clip2() == 0 and self.Secondary.AutoReloadOnEmpty ) then
1569 self:Reload()
1570 end
1571
1572 if ( not self:EventActive( "burst" )) then
1573 // The following code prevents the player from tapping the firebutton repeatedly
1574 // to simulate full auto and retaining the single shot accuracy of single fire
1575 local iShotsFired = self:GetShotsFired()
1576 local flShotTime = self:GetReduceShotTime()
1577
1578 if ( flShotTime == -1 ) then
1579 if ( iShotsFired > self.MaxShots ) then
1580 self:SetShotsFired( self.MaxShots )
1581 end
1582
1583 self:SetReduceShotTime( flCurTime + self.ShotInitialDecreaseTime )
1584 elseif ( iShotsFired ~= 0 ) then
1585 if ( flShotTime < flCurTime ) then
1586 self:SetShotsFired( iShotsFired - 1 )
1587 self:SetReduceShotTime( flCurTime + self.ShotDecreaseTime )
1588 end
1589 end
1590 end
1591end
1592
1593-- Normal think function replacement
1594function SWEP:ItemFrame()
1595end
1596-- FIXME: Add queued reloading and check out secondary fire behaviour
1597--- Attack
1598function SWEP:CanPrimaryAttack( iIndex )
1599 if ( self:EventActive( "fire" ) or self:GetNextPrimaryFire() == -1 ) then
1600 return false
1601 end
1602
1603 local pPlayer = self:GetOwner()
1604
1605 -- Make sure player is at least valid for the methods below
1606 if ( pPlayer == NULL ) then
1607 return false
1608 end
1609
1610 local iClip = self:Clip1()
1611 local iWaterLevel = pPlayer:WaterLevel()
1612
1613 if ( iWaterLevel == 0 and not (self.Primary.FireInAir or pPlayer:OnGround()) ) then
1614 return false
1615 end
1616
1617 local bEmpty = pPlayer:GetAmmoCount( self:GetPrimaryAmmoName() ) == 0
1618
1619 -- In the middle of a reload
1620 if ( self:EventActive( "reload" )) then
1621 if ( iClip == 0 or bEmpty and iClip == -1 ) then
1622 return false
1623 end
1624
1625 if ( self.SingleReload.Enable and self.SingleReload.QueuedFire ) then
1626 -- FIXME: Use first available ammo or SequenceEnd
1627 self:AddEvent( "fire", self:SequenceEnd( iIndex ), function()
1628 self:PrimaryAttack()
1629 self:RemoveEvent( "reload" )
1630 self:SetNextReload(0)
1631
1632 if ( bSinglePlayer ) then
1633 net.Start( "GSWeapons-Finish reload" )
1634 net.Send( pPlayer )
1635 end
1636
1637 return true
1638 end )
1639
1640 return false
1641 end
1642
1643 -- Interrupt the reload to fire
1644 if ( not self.Primary.InterruptReload or not self.Primary.FireUnderwater and iWaterLevel == 3 ) then
1645 return false
1646 end
1647
1648 -- Stop the reload
1649 self:RemoveEvent( "reload" )
1650 self:SetNextReload(0)
1651
1652 if ( bSinglePlayer ) then
1653 net.Start( "GSWeapons-Finish reload" )
1654 net.Send( pPlayer )
1655 end
1656 end
1657
1658 -- By default, clip has priority over water
1659 if ( iClip == 0 or bEmpty and iClip == -1 and self:GetDefaultClip1() ~= -1 ) then
1660 self:HandleFireOnEmpty( false, iIndex )
1661
1662 return false
1663 end
1664
1665 if ( not self.Primary.FireUnderwater and iWaterLevel == 3 ) then
1666 self:HandleFireUnderwater( false, iIndex )
1667
1668 return false
1669 end
1670
1671 return true
1672end
1673
1674-- Will only be called serverside in single-player
1675function SWEP:PrimaryAttack()
1676 if ( self:CanPrimaryAttack(0) ) then
1677 self:Shoot( self:SpecialActive(0), 0 )
1678
1679 return true
1680 end
1681
1682 return false
1683end
1684
1685function SWEP:CanSecondaryAttack( iIndex )
1686 if ( self:EventActive( "fire" ) or self:GetNextSecondaryFire() == -1 ) then
1687 return false
1688 end
1689
1690 local pPlayer = self:GetOwner()
1691
1692 if ( pPlayer == NULL ) then
1693 return false
1694 end
1695
1696 local iClip = self.CheckClip1ForSecondary and self:Clip1() or self:Clip2()
1697 local iWaterLevel = pPlayer:WaterLevel()
1698
1699 if ( iWaterLevel == 0 and not (self.Secondary.FireInAir or pPlayer:OnGround()) ) then
1700 return false
1701 end
1702
1703 local bEmpty = pPlayer:GetAmmoCount( self.CheckClip1ForSecondary and self:GetPrimaryAmmoName() or self:GetSecondaryAmmoName() ) == 0
1704
1705 if ( self:EventActive( "reload" )) then
1706 if ( iClip == 0 or bEmpty and iClip == -1 ) then
1707 return false
1708 end
1709
1710 if ( self.SingleReload.Enable and self.SingleReload.QueuedFire ) then
1711 self:AddEvent( "fire", self:SequenceEnd( iIndex ), function()
1712 self:SecondaryAttack()
1713 self:RemoveEvent( "reload" )
1714 self:SetNextReload(0)
1715
1716 if ( bSinglePlayer ) then
1717 net.Start( "GSWeapons-Finish reload" )
1718 net.Send( pPlayer )
1719 end
1720
1721 return true
1722 end )
1723
1724 return false
1725 end
1726
1727 if ( not self.Secondary.InterruptReload or not self.Secondary.FireUnderwater and iWaterLevel == 3 ) then
1728 return false
1729 end
1730
1731 self:RemoveEvent( "reload" )
1732 self:SetNextReload(0)
1733
1734 if ( bSinglePlayer ) then
1735 net.Start( "GSWeapons-Finish reload" )
1736 net.Send( pPlayer )
1737 end
1738 end
1739
1740 if ( iClip == 0 or bEmpty and iClip == -1 and (self.CheckClip1ForSecondary and self:GetDefaultClip1() or self:GetDefaultClip2()) ~= -1 ) then
1741 self:HandleFireOnEmpty( true, iIndex )
1742
1743 return false
1744 end
1745
1746 if ( not self.Secondary.FireUnderwater and iWaterLevel == 3 ) then
1747 self:HandleFireUnderwater( true, iIndex )
1748
1749 return false
1750 end
1751
1752 return true
1753end
1754
1755function SWEP:SecondaryAttack()
1756end
1757
1758function SWEP:GetShootClip( bSecondary --[[= false]] )
1759 return self:Clip1()
1760end
1761
1762function SWEP:GetWorldModelIndex()
1763 return 0
1764end
1765
1766function SWEP:SetShootClip( iClip, bSecondary )
1767 self:SetClip1( iClip )
1768end
1769
1770function SWEP:Shoot( bSecondary --[[= false]], iIndex --[[= 0]], Play --[[= "shoot"]], iClipDeduction --[[= 1]] )
1771 if ( not iClipDeduction ) then
1772 iClipDeduction = 1
1773 end
1774
1775 if ( not iIndex ) then
1776 iIndex = 0
1777 end
1778
1779 local iClip = self:GetShootClip( bSecondary )
1780 local bDeductClip = iClip ~= -1
1781
1782 -- Check just in-case the weapon's CanPrimary/SecondaryAttack doesn't check properly
1783 -- Do NOT let the clip overflow
1784 if ( bDeductClip and iClipDeduction > iClip ) then
1785 error( self:GetClass() .. " (weapon_gs_base) Clip overflowed in Shoot! Add check to CanPrimary/SecondaryAttack" )
1786 end
1787
1788 if ( not Play ) then
1789 Play = "shoot"
1790 end
1791
1792 local tbl = self:GetShotTable( bSecondary )
1793 local bBurst = self:BurstEnabled( iIndex ) and (not bDeductClip or iClip >= iClipDeduction * 2)
1794 local flCooldown = self:GetSpecialKey( "Cooldown", bSecondary )
1795 local pPlayer = self:GetOwner()
1796 local sSound = isnumber( Play ) and "shoot" or Play
1797
1798 if ( bDeductClip ) then
1799 iClip = iClip - iClipDeduction
1800 self:SetShootClip( iClip, bSecondary )
1801 end
1802
1803 if ( bBurst ) then
1804 local tBurst = self.Burst
1805 local tTimes = tBurst.Times
1806 local flLastTime = tTimes[1]
1807 local iCount = tBurst.Count
1808 local iCurCount = 1
1809
1810 self:AddEvent( "burst", flLastTime, function()
1811 if ( bDeductClip ) then
1812 iClip = iClip - iClipDeduction
1813 self:SetShootClip( iClip, bSecondary )
1814 end
1815
1816 pPlayer:SetAnimation( PLAYER_ATTACK1 )
1817
1818 self:SetShotsFired( self:GetShotsFired() + 1 )
1819 self:DoMuzzleFlash( iIndex )
1820 self:PlaySound( sSound, iIndex )
1821 local bActivity = false
1822
1823 if ( not tBurst.SingleActivity ) then
1824 bActivity = self:PlayActivity( Play, iIndex )
1825 end
1826
1827 self:UpdateBurstShotTable( tbl, bSecondary )
1828
1829 local flCurTime = CurTime()
1830 self:SetLastShootTime( flCurTime )
1831 self.FireFunction( pPlayer, tbl )
1832
1833 if ( iCurCount == iCount or bDeductClip and iClip < iClipDeduction ) then
1834 if ( self.DoPump ) then
1835 self:SetNextPrimaryFire(-1)
1836 self:SetNextSecondaryFire(-1)
1837 self:SetNextReload(-1)
1838 self:SetReduceShotTime(-1)
1839
1840 self:AddEvent( "pump", bActivity and self:SequenceLength( iIndex ) or 0, function()
1841 self:PlaySound( "pump", iIndex )
1842
1843 -- Cooldown is sequence based
1844 local flNextTime = CurTime() + (self:PlayActivity( "pump", iIndex ) and self:SequenceLength( iIndex ) or 0)
1845 self:SetNextPrimaryFire( flNextTime )
1846 self:SetNextSecondaryFire( flNextTime )
1847 self:SetNextReload( flNextTime )
1848
1849 return true
1850 end )
1851 else
1852 local flNewTime = flCurTime + flCooldown
1853 self:SetNextPrimaryFire( flNewTime )
1854 self:SetNextSecondaryFire( flNewTime )
1855 self:SetNextReload( flNewTime )
1856 self:SetReduceShotTime(-1)
1857 end
1858
1859 return true
1860 end
1861
1862 iCurCount = iCurCount + 1
1863 flLastTime = tTimes[iCurCount] or flLastTime
1864
1865 return flLastTime
1866 end )
1867 else
1868 self:SetReduceShotTime(-1)
1869
1870 local tZoom = self.Zoom
1871
1872 if ( tZoom.UnzoomOnFire ) then
1873 local iLevel = self:GetZoomLevel()
1874
1875 if ( iLevel ~= 0 ) then
1876 self:SetZoomLevel(0) -- Disable scope overlay
1877 pPlayer:SetFOV( 0, tZoom.Times.Fire )
1878
1879 if ( tZoom.HideViewModel ) then
1880 if ( self.m_tUseViewModel[0] ) then
1881 local pViewModel = pPlayer:GetViewModel(0)
1882 pViewModel:SetVisible( true )
1883 end
1884
1885 if ( self.m_tUseViewModel[1] ) then
1886 local pViewModel = pPlayer:GetViewModel(1)
1887 pViewModel:SetVisible( true )
1888 end
1889
1890 if ( self.m_tUseViewModel[2] ) then
1891 local pViewModel = pPlayer:GetViewModel(2)
1892 pViewModel:SetVisible( true )
1893 end
1894 end
1895
1896 -- Don't rezoom if the clip is empty
1897 if ( iClip ~= 0 ) then
1898 self:AddEvent( "rezoom", flCooldown, function()
1899 local flRezoom = tZoom.Times.Rezoom
1900 self:SetZoomActiveTime( flRezoom )
1901 self:SetZoomLevel( iLevel )
1902 self:SetNextSecondaryFire( flRezoom )
1903 pPlayer:SetFOV( tZoom.FOV[iLevel], flRezoom )
1904
1905 if ( not tZoom.FireDuringZoom ) then
1906 self:SetNextPrimaryFire( flRezoom )
1907 end
1908
1909 if ( tZoom.HideViewModel ) then
1910 local pPlayer = self:GetOwner()
1911
1912 if ( self.m_tUseViewModel[0] ) then
1913 local pViewModel = pPlayer:GetViewModel(0)
1914 pViewModel:SetVisible( false )
1915 end
1916
1917 if ( self.m_tUseViewModel[1] ) then
1918 local pViewModel = pPlayer:GetViewModel(1)
1919 pViewModel:SetVisible( false )
1920 end
1921
1922 if ( self.m_tUseViewModel[2] ) then
1923 local pViewModel = pPlayer:GetViewModel(2)
1924 pViewModel:SetVisible( false )
1925 end
1926 end
1927
1928 return true
1929 end )
1930 end
1931 end
1932 end
1933 end
1934
1935 pPlayer:SetAnimation( PLAYER_ATTACK1 )
1936
1937 self:SetShotsFired( self:GetShotsFired() + 1 )
1938 self:DoMuzzleFlash( iIndex )
1939 self:PlaySound( sSound, iIndex )
1940 local bActivity = self:PlayActivity( Play, iIndex )
1941
1942 local flCurTime = CurTime()
1943 self:SetLastShootTime( flCurTime )
1944
1945 -- The zoom level needs to be set before PlayActivity but the times need to be set after
1946 -- So do two seperate burst blocks
1947 if ( bBurst or self.DoPump ) then
1948 self:SetNextPrimaryFire(-1)
1949 self:SetNextSecondaryFire(-1)
1950 self:SetNextReload(-1)
1951 else
1952 local flNextTime = flCurTime + flCooldown
1953 self:SetNextPrimaryFire( flNextTime )
1954 self:SetNextSecondaryFire( flNextTime )
1955 self:SetNextReload( flNextTime )
1956 end
1957
1958 self.FireFunction( pPlayer, tbl )
1959
1960 if ( not bBurst and self.DoPump ) then
1961 self:AddEvent( "pump", bActivity and self:SequenceLength( iIndex ) or 0, function()
1962 self:PlaySound( "pump", iIndex )
1963
1964 -- Cooldown is sequence based
1965 local flNextTime = CurTime() + (self:PlayActivity( "pump", iIndex ) and self:SequenceLength( iIndex ) or 0)
1966 self:SetNextPrimaryFire( flNextTime )
1967 self:SetNextSecondaryFire( flNextTime )
1968 self:SetNextReload( flNextTime )
1969
1970 return true
1971 end )
1972 end
1973
1974 if ( self:GetDeployed() == iIndex + 1 and self.m_pDeployEntity:IsPlayer() ) then
1975 self.m_pDeployEntity:SetDSP( 32, false )
1976 end
1977
1978 self:Punch( bSecondary )
1979
1980 return bActivity
1981end
1982
1983function SWEP:UpdateBurstShotTable( tbl, bSecondary )
1984 tbl.Dir = self:GetShootAngles( bSecondary ):Forward()
1985 tbl.Src = self:GetShootSrc( bSecondary )
1986end
1987
1988function SWEP:Throw( iType --[[= GRENADE_THROW]], iIndex --[[= 0]] )
1989 if ( not iIndex ) then
1990 iIndex = 0
1991 end
1992
1993 -- Complicated way to condense the throw data into one DTVar
1994 self:SetShouldThrow( (iType or GRENADE_THROW) + GRENADE_COUNT * iIndex )
1995
1996 self:PlaySound( "pullback", iIndex )
1997 local bActivity = self:PlayActivity( "pullback", iIndex )
1998
1999 self:SetNextPrimaryFire(-1)
2000 self:SetNextSecondaryFire(-1)
2001 self:SetNextReload(-1)
2002 self:SetNextIdle(-1)
2003
2004 return bActivity
2005end
2006
2007-- Shared for the HL1 grenade
2008function SWEP:EmitGrenade()
2009end
2010
2011-- FIXME: Check all of this
2012function SWEP:GetMeleeTrace( tbl, vForward, bNoMiss )
2013 local tr
2014
2015 if ( tbl.output ) then
2016 tr = tbl.output
2017 else
2018 tr = {}
2019 tbl.output = tr
2020 end
2021
2022 util.TraceLine( tbl )
2023
2024 if ( tr.Fraction == 1 ) then
2025 local tMelee = self.Melee
2026
2027 // hull is +/- 16, so use cuberoot of 2 to determine how big the hull is from center to the corner point
2028 -- Comment is wrong; it's actually the sqrt(3)
2029 tbl.endpos = tbl.endpos - vForward * tMelee.HullRadius
2030 tbl.mins = -tMelee.TestHull
2031 tbl.maxs = tMelee.TestHull
2032
2033 util.TraceHull( tbl )
2034
2035 if ( not (tr.Fraction == 1 or tr.Entity == NULL) ) then
2036 local vTarget = tr.Entity:GetPos() - tr.StartPos
2037 vTarget:Normalize()
2038
2039 // YWB: Make sure they are sort of facing the guy at least...
2040 if ( not bNoMiss and vTarget:Dot( vForward ) < tMelee.DotRange ) then
2041 // Force amiss
2042 tr.Fraction = 1
2043 tr.Entity = NULL
2044 tr.Hit = false
2045 bMiss = true
2046 else
2047 util.FindHullIntersection( tbl, tr )
2048 end
2049 end
2050 end
2051
2052 return tr
2053end
2054
2055function SWEP:Swing( bSecondary, iIndex )
2056 local pPlayer = self:GetOwner()
2057 pPlayer:LagCompensation( true )
2058
2059 local tMelee = self.Melee
2060 local vSrc = self:GetShootSrc( bSecondary )
2061 local vForward = self:GetShootAngles( bSecondary ):Forward()
2062
2063 local tbl = {
2064 start = vSrc,
2065 endpos = vSrc + vForward * self:GetSpecialKey( "Range", bSecondary ),
2066 mask = tMelee.Mask,
2067 filter = pPlayer
2068 }
2069 local tr = self:GetMeleeTrace( tbl, vForward )
2070 local flDelay = self:GetSpecialKey( "Delay", bSecondary, vForward )
2071 local bActivity
2072
2073 if ( tr.Fraction == 1 ) then
2074 self:PlaySound( "miss", iIndex )
2075 bActivity = self:PlayActivity( bSecondary and "miss_alt" or "miss", iIndex )
2076 else
2077 bActivity = self:PlayActivity( bSecondary and "hit_alt" or "hit", iIndex )
2078 end
2079
2080 if ( flDelay == -1 ) then
2081 self:Smack( tr, vForward, bSecondary, iIndex )
2082 else
2083 if ( bActivity ) then
2084 local pViewModel = pPlayer:GetViewModel( iIndex )
2085 local flRate = pViewModel:GetPlaybackRate()
2086 flDelay = flDelay / (flRate == 0 and 1 or flRate < 0 and -flRate or flRate)
2087 end
2088
2089 self:AddEvent( "smack", flDelay, function()
2090 local bRetrace = not tMelee.StrictTrace -- FIXME
2091
2092 if ( bRetrace ) then
2093 pPlayer:LagCompensation( true )
2094
2095 local vSrc = self:GetShootSrc( bSecondary )
2096 local vForward = self:GetShootAngles( bSecondary ):Forward()
2097
2098 tbl.start = vSrc
2099 tbl.endpos = vSrc + vForward * self:GetSpecialKey( "Range", bSecondary )
2100 tbl.mask = tMelee.Mask
2101 tbl.filter = pPlayer
2102 tbl.output = tr
2103
2104 self:GetMeleeTrace( tbl, vForward )
2105 end
2106
2107 self:Smack( tr, vForward, bSecondary, iIndex )
2108
2109 if ( bRetrace ) then
2110 pPlayer:LagCompensation( false )
2111 end
2112
2113 return true
2114 end )
2115 end
2116
2117 // Setup out next attack times
2118 local flCurTime = CurTime()
2119 local flCooldown = self:GetSpecialKey( "Cooldown", bSecondary )
2120 self:SetLastShootTime( flCurTime )
2121 self:SetNextPrimaryFire( flCurTime + flCooldown )
2122 self:SetNextSecondaryFire( flCurTime + (bActivity and self:SequenceLength( iIndex ) or flCooldown) )
2123
2124 pPlayer:LagCompensation( false )
2125
2126 return bActivity
2127end
2128
2129function SWEP:Smack( tr, vForward, bSecondary, iIndex )
2130 local tMelee = self.Melee
2131 local pPlayer = self:GetOwner()
2132 local vSrc = self:GetShootSrc( bSecondary )
2133 local tbl = {}
2134
2135 local bFirstTimePredicted = IsFirstTimePredicted()
2136 local bNoWater = true
2137
2138 if ( bFirstTimePredicted ) then
2139 local bHitWater = bit.band( util.PointContents( vSrc ), MASK_WATER ) ~= 0
2140 local bEndNotWater = bit.band( util.PointContents( tr.HitPos ), MASK_WATER ) == 0
2141 local trSplash = bHitWater and bEndNotWater and
2142 util.TraceLine({
2143 start = tr.HitPos,
2144 endpos = vSrc,
2145 mask = MASK_WATER
2146 })
2147 or not (bHitWater or bEndNotWater) and
2148 util.TraceLine({
2149 start = vSrc,
2150 endpos = tr.HitPos,
2151 mask = MASK_WATER
2152 })
2153
2154 if ( trSplash and not self:DoSplashEffect( trSplash )) then
2155 local data = EffectData()
2156 data:SetOrigin( trSplash.HitPos )
2157 data:SetScale(8)
2158
2159 if ( bit.band( util.PointContents( trSplash.HitPos ), CONTENTS_SLIME ) ~= 0 ) then
2160 data:SetFlags( FX_WATER_IN_SLIME )
2161 end
2162
2163 util.Effect( "watersplash", data )
2164 end
2165 end
2166
2167 // Send the anim
2168 pPlayer:SetAnimation( PLAYER_ATTACK1 )
2169
2170 if ( tr.Fraction ~= 1 ) then
2171 self:PlaySound( (tr.Entity:IsPlayer() or tr.Entity:IsNPC()) and "hit" or "hitworld", iIndex )
2172 self:SmackDamage( tr, vForward, bSecondary )
2173
2174 if ( bNoWater and not self:DoImpactEffect( tr, tMelee.DamageType ) and bFirstTimePredicted ) then
2175 local data = EffectData()
2176 data:SetOrigin( tr.HitPos )
2177 data:SetStart( vSrc )
2178 data:SetSurfaceProp( tr.SurfaceProps )
2179 data:SetDamageType( tMelee.DamageType )
2180 data:SetHitBox( tr.HitBox )
2181 data:SetEntity( tr.Entity )
2182 util.Effect( "Impact", data )
2183 end
2184 end
2185end
2186
2187function SWEP:SmackDamage( tr, vForward, bSecondary )
2188 local flDamage = self:GetSpecialKey( "Damage", bSecondary )
2189 local info = DamageInfo()
2190 info:SetAttacker( self:GetOwner() )
2191 info:SetInflictor( self )
2192 info:SetDamage( flDamage )
2193 info:SetDamageType( self.Melee.DamageType )
2194 info:SetDamagePosition( tr.HitPos )
2195 info:SetReportedPosition( tr.StartPos )
2196 --info:SetDamageForce( vForward * info:GetBaseDamage() * self:GetSpecialKey( "Force", bSecondary ) * (1 / (flDamage < 1 and 1 or flDamage)) * phys_pushscale:GetFloat() ) FIXME
2197 tr.Entity:DispatchTraceAttack( info, tr, vForward )
2198end
2199
2200function SWEP:Silence( iIndex )
2201 self:PlaySound( "silence", iIndex )
2202 local flNewTime = self:PlayActivity( "silence", iIndex ) and self:SequenceLength( iIndex ) or 0
2203
2204 self:AddEvent( "silence", flNewTime, function()
2205 local iSilence = self:GetSilenced()
2206 iIndex = 2^iIndex
2207 self:SetSilenced( bit.band( iSilence, iIndex ) == 0 and iSilence + iIndex or iSilence - iIndex )
2208
2209 return true
2210 end )
2211
2212 flNewTime = flNewTime + CurTime()
2213 self:SetNextPrimaryFire( flNewTime )
2214 self:SetNextSecondaryFire( flNewTime )
2215 self:SetNextReload( flNewTime )
2216end
2217
2218function SWEP:ToggleBurst( iIndex )
2219 self:PlaySound( "burst", iIndex )
2220 self:SetNextSecondaryFire( CurTime() + self.Burst.Cooldown )
2221
2222 local iBurst = self:GetBurst()
2223 iIndex = 2^iIndex
2224 self:SetBurst( bit.band( iBurst, iIndex ) == 0 and iBurst + iIndex or iBurst - iIndex )
2225
2226 self:GetOwner():PrintMessage( HUD_PRINTCENTER, bInBurst and "#GSWeapons_FromBurstFire" or "#GSWeapons_ToBurstFire" )
2227end
2228
2229-- Doesn't matter which viewmodel is zoomed in since no specific anims are assosiated with it
2230function SWEP:AdvanceZoom( iIndex )
2231 local tZoom = self.Zoom
2232 local iLevel = (self:GetZoomLevel() + 1) % (tZoom.Levels + 1)
2233 local iFOV = iLevel == 0 and 0 or tZoom.FOV[iLevel]
2234 local flTime = tZoom.Times[iLevel] or tZoom.Times[0]
2235 self:PlaySound( "zoom", iIndex )
2236
2237 if ( iFOV and flTime ) then
2238 self:SetZoomLevel( iLevel )
2239
2240 local pPlayer = self:GetOwner()
2241 pPlayer:SetFOV( iFOV, flTime )
2242
2243 -- Don't hide again if the VM is already hidden
2244 if ( tZoom.HideViewModel and iLevel < 2 ) then
2245 local bVisible = iLevel == 0
2246
2247 if ( self.m_tUseViewModel[0] ) then
2248 local pViewModel = pPlayer:GetViewModel(0)
2249 pViewModel:SetVisible( bVisible )
2250 end
2251
2252 if ( self.m_tUseViewModel[1] ) then
2253 local pViewModel = pPlayer:GetViewModel(1)
2254 pViewModel:SetVisible( bVisible )
2255 end
2256
2257 if ( self.m_tUseViewModel[2] ) then
2258 local pViewModel = pPlayer:GetViewModel(2)
2259 pViewModel:SetVisible( bVisible )
2260 end
2261 end
2262 else
2263 ErrorNoHalt( string.format( "%s (weapon_gs_base) Zoom level or time %u not defined! Zooming out..", self:GetClass(), iLevel ))
2264 self:SetZoomLevel(0)
2265
2266 local pPlayer = self:GetOwner()
2267 pPlayer:SetFOV(0, 0)
2268
2269 if ( tZoom.HideViewModel ) then
2270 if ( self.m_tUseViewModel[0] ) then
2271 local pViewModel = pPlayer:GetViewModel(0)
2272 pViewModel:SetVisible( true )
2273 end
2274
2275 if ( self.m_tUseViewModel[1] ) then
2276 local pViewModel = pPlayer:GetViewModel(1)
2277 pViewModel:SetVisible( true )
2278 end
2279
2280 if ( self.m_tUseViewModel[2] ) then
2281 local pViewModel = pPlayer:GetViewModel(2)
2282 pViewModel:SetVisible( true )
2283 end
2284 end
2285 end
2286
2287 local flNextTime = CurTime()
2288 self:SetNextSecondaryFire( flNextTime + tZoom.Cooldown )
2289
2290 flNextTime = flNextTime + flTime
2291
2292 if ( iIndex == 1 ) then
2293 self:SetZoomActiveTime1( flNextTime )
2294 elseif ( iIndex == 2 ) then
2295 self:SetZoomActiveTime2( flNextTime )
2296 else
2297 self:SetZoomActiveTime( flNextTime )
2298 end
2299
2300 if ( not tZoom.FireDuringZoom ) then
2301 self:SetNextPrimaryFire( flNextTime )
2302 end
2303end
2304
2305function SWEP:ToggleIronSights( iIndex --[[= 0]] )
2306 local flCurTime = CurTime()
2307 local iSights = self:GetIronSights()
2308 iIndex = 2^iIndex
2309 self:SetIronSights( bit.band( iSights, iIndex ) == 0 and iSights + iIndex or iSights - iIndex )
2310
2311 if ( iIndex == 1 ) then
2312 local flTotalTime = self.IronSights.ZoomTime[2] or self.IronSights.ZoomTime[1]
2313 local flRemaining = flCurTime - self:GetZoomActiveTime1()
2314 self:SetZoomActiveTime1( flCurTime + (flRemaining < flTotalTime
2315 and flTotalTime - flReamining or flTotalTime) )
2316 elseif ( iIndex == 2 ) then
2317 local flTotalTime = self.IronSights.ZoomTime[3] or self.IronSights.ZoomTime[2] or self.IronSights.ZoomTime[1]
2318 local flRemaining = flCurTime - self:GetZoomActiveTime2()
2319 self:SetZoomActiveTime2( flCurTime + (flRemaining < flTotalTime
2320 and flTotalTime - flReamining or flTotalTime) )
2321 else
2322 local flTotalTime = self.IronSights.ZoomTime[1]
2323 local flRemaining = flCurTime - self:GetZoomActiveTime()
2324 self:SetZoomActiveTime( flCurTime + (flRemaining < flTotalTime
2325 and flTotalTime - flReamining or flTotalTime) )
2326 end
2327end
2328
2329local function TestDeployAngle( vForward, vStart, vTraceHeight, flStartOffset, flDownOffset, flMaxForwardDist, flStepSize, iAttempts, bDebug, tbl, tr )
2330 // sandbags are around 50 units high. Shouldn't be able to deploy on anything a lot higher than that
2331
2332 // optimal standing height ( for animation's sake ) is around 42 units
2333 // optimal ducking height is around 20 units ( 20 unit high object, plus 8 units of gun )
2334
2335 // Start one half box width away from the edge of the player hull
2336 local vForwardStart = vStart + vForward * flStartOffset
2337
2338 tbl.start = vForwardStart
2339 tbl.endpos = vForwardStart + vForward * flMaxForwardDist
2340 util.TraceHull( tbl )
2341
2342 if ( bDebug ) then
2343 debugoverlay.Line( vForwardStart, tbl.endpos, DEBUG_LENGTH, color_debug )
2344 debugoverlay.Box( vForwardStart, tbl.mins, tbl.maxs, DEBUG_LENGTH, color_altdebug )
2345 debugoverlay.Box( tr.HitPos, tbl.mins, tbl.maxs, DEBUG_LENGTH, color_debug )
2346 end
2347
2348 // Test forward, are we trying to deploy into a solid object?
2349 if ( tr.Fraction ~= 1 ) then
2350 return false
2351 end
2352
2353 local vDownTraceStart = vStart + vForward * flDownOffset
2354
2355 // search down from the forward trace
2356 // use the farthest point first. If that fails, move towards the player a few times
2357 // to see if they are trying to deploy on a thin railing
2358
2359 local flHighestTraceEnd = vDownTraceStart[3] + vTraceHeight[3]
2360 local pBestDeployEnt = NULL
2361 local bFound = false
2362
2363 tbl.start = vDownTraceStart
2364 tbl.endpos = vDownTraceStart + vTraceHeight // trace forward one box width
2365
2366 while ( iAttempts > 0 ) do
2367 util.TraceHull( tbl )
2368
2369 if ( bDebug ) then
2370 debugoverlay.Line( vDownTraceStart, tr.HitPos, DEBUG_LENGTH, color_debug )
2371 debugoverlay.Box( vDownTraceStart, tbl.mins, tbl.maxs, DEBUG_LENGTH, color_altdebug )
2372 debugoverlay.Box( tr.HitPos, tbl.mins, tbl.maxs, DEBUG_LENGTH, color_debug )
2373 end
2374
2375 local bSuccess = not ( tr.Fraction == 1 or tr.StartSolid or tr.AllSolid )
2376
2377 // if this is the first one found, set found flag
2378 if ( bSuccess ) then
2379 if ( not bFound ) then
2380 bFound = true
2381 end
2382 elseif ( bFound ) then
2383 // it failed and we have some data. break here
2384 break
2385 end
2386
2387 local flTraceHeight = tr.HitPos[3]
2388
2389 // if this trace is better ( higher ) use this one
2390 if ( flTraceHeight > flHighestTraceEnd ) then
2391 flHighestTraceEnd = flTraceHeight
2392 pBestDeployEnt = tr.Entity
2393 end
2394
2395 iAttempts = iAttempts - 1
2396
2397 // move towards the player, looking for a better height to deploy on
2398 vDownTraceStart:Add( vForward * flStepSize )
2399 end
2400
2401 if ( not bFound or pBestDeployEnt == NULL ) then
2402 return false
2403 end
2404
2405 return true, flHighestTraceEnd, pBestDeployEnt
2406end
2407
2408local DEPLOY_DOWNTRACE_OFFSET = 16 // yay for magic numbers
2409
2410function SWEP:TestDeploy( bDebug )
2411 local tDeploy = self.BipodDeploy
2412
2413 -- Bound check
2414 if ( bTestLimits and tDeploy.MaxYaw <= 0 ) then
2415 return false
2416 end
2417
2418 local pPlayer = self:GetOwner()
2419
2420 -- Don't deploy in the air, underwater, or during a movement transition
2421 if ( pPlayer:GetGroundEntity() == NULL ) then
2422 return false
2423 end
2424
2425 -- Deploy on what the player is looking at, not where they're necessarily facing
2426 local aEyes = pPlayer:EyeAngles()
2427 local flPitch = aEyes[1]
2428
2429 -- In-case EyeAngles isn't bounded correctly
2430 -- FIXME: Can use modulo, but I forgot how it handles negative numbers
2431 if ( flPitch > 180 ) then
2432 flPitch = flPitch - 360
2433 end
2434
2435 -- Don't deploy while the player is looking too high or low
2436 if ( flPitch > tDeploy.MaxPitch or flPitch < tDeploy.MinPitch ) then
2437 return false
2438 end
2439
2440 local flTraceBox = tDeploy.BoxSize
2441 local flMaxForwardDist = tDeploy.ForwardTrace - 2 * flTraceBox
2442
2443 -- Don't trace backward
2444 if ( flMaxForwardDist < 0 ) then
2445 return false
2446 end
2447
2448 aEyes[1] = 0 -- Now that the bounds have been checked, only check for y-axis rotation
2449 local _, vMaxs = pPlayer:GetHull()
2450 local flMaxX = vMaxs[1]
2451 local flStartOffset = flMaxX + flTraceBox
2452 local flDownTrace = tDeploy.DownTrace
2453 local flDownOffset = flMaxX + flDownTrace
2454 local vTraceBox = Vector( flTraceBox, flTraceBox, flTraceBox )
2455 local vStart = pPlayer:GetPos()
2456 local flStartHeight = vStart[3]
2457 local flHeightOffset = flTraceBox + flStartHeight - DEPLOY_DOWNTRACE_OFFSET
2458
2459 local bDeployed = self:GetDeployed() ~= 0
2460 local pFilter
2461
2462 if ( bDeployed ) then
2463 pFilter = player.GetAll()
2464 local pDeployedOn = self.m_pDeployEntity
2465
2466 if ( pDeployedOn:IsPlayer() ) then
2467 local iPlayers = player.GetCount()
2468
2469 for i = 1, iPlayers do
2470 if ( pFilter[i] == pDeployedOn ) then
2471 -- Remove the deployed entity from the filter
2472 pFilter[i] = pFilter[iPlayers]
2473 pFilter[iPlayers] = nil
2474 end
2475 end
2476 end
2477 else
2478 pFilter = pPlayer
2479 end
2480
2481 if ( bDeployed and self.m_bDeployCrouched or pPlayer:Crouching() ) then
2482 local _, vMaxs = pPlayer:GetHullDuck()
2483 flStartHeight = flStartHeight + vMaxs[3] - flTraceBox - flDownTrace / 4
2484 else
2485 -- FIXME: Source of 60? Is related to DoD player height
2486 flStartHeight = flStartHeight + 60 - flTraceBox - flDownTrace / 4
2487 end
2488
2489 vStart[3] = flStartHeight
2490
2491 local tr = {}
2492 local tbl = {
2493 mask = MASK_SOLID,
2494 filter = pFilter,
2495 mins = -vTraceBox,
2496 maxs = vTraceBox,
2497 output = tr
2498 }
2499
2500 local vTraceHeight = pPlayer:OBBMaxs()
2501 vTraceHeight[1] = 0
2502 vTraceHeight[2] = 0
2503 vTraceHeight[3] = -vTraceHeight[3]
2504
2505 -- Returns
2506 local bSuccess, flDeployedHeight, pDeployedOn = TestDeployAngle( aEyes:Forward(), vStart, vTraceHeight, flStartOffset, flDownOffset, flMaxForwardDist, tDeploy.StepSize, tDeploy.TraceAttempts, bDebug, tbl, tr )
2507
2508 // can't deploy here, drop out early
2509 -- If we can't deploy right in front of us, no use in checking the further angles
2510 if ( not bSuccess or flDeployedHeight - flHeightOffset < tDeploy.MinDeployHeight ) then
2511 return false
2512 end
2513
2514 local flYaw = aEyes[2]
2515 local flLeft = 0
2516 local flYawLimitLeft = 0
2517
2518 repeat
2519 flLeft = flLeft + tDeploy.Delta
2520 aEyes[2] = flYaw + flLeft
2521 local bSuccess, flTestDeployHeight = TestDeployAngle( aEyes:Forward(), vStart, vTraceHeight, flStartOffset, flDownOffset, flMaxForwardDist, tDeploy.StepSize, tDeploy.TraceAttempts, bDebug, tbl, tr )
2522
2523 // don't allow yaw to a position that is too different in height
2524 if ( not bSuccess or math.abs( flDeployedHeight - flTestDeployHeight ) > tDeploy.MaxHeightDifference ) then
2525 break
2526 end
2527
2528 flYawLimitLeft = flLeft
2529 until ( flLeft > tDeploy.MaxYaw )
2530
2531 // we already tested directly ahead and it was clear. skip one test
2532 local flRight = 0
2533 local flYawLimitRight = 0
2534
2535 // Sweep Right
2536 while ( flRight <= tDeploy.MaxYaw ) do
2537 flRight = flRight + tDeploy.Delta
2538 aEyes[2] = flYaw - flRight
2539 local bSuccess, flTestDeployHeight = TestDeployAngle( aEyes:Forward(), vStart, vTraceHeight, flStartOffset, flDownOffset, flMaxForwardDist, tDeploy.StepSize, tDeploy.TraceAttempts, bDebug, tbl, tr )
2540
2541 // don't allow yaw to a position that is too different in height
2542 if ( not bSuccess or math.abs( flDeployedHeight - flTestDeployHeight ) > tDeploy.MaxHeightDifference ) then
2543 break
2544 end
2545
2546 flYawLimitRight = flRight
2547 end
2548
2549 -- Eek, no references in Lua
2550 return true, flDeployedHeight - flHeightOffset, pDeployedOn, flYaw, flYawLimitLeft, -flYawLimitRight
2551end
2552
2553function SWEP:ShouldUndeploy()
2554 local bSuccess, flDeployedHeight, pDeployedOn, flYaw, flYawLimitLeft, flYawLimitRight = self:TestDeploy( false )
2555
2556 // If the entity we were deployed on has changed, or has moved, the origin
2557 // of it will be different. If so, recalc our yaw limits.
2558 if ( bSuccess and math.abs( self.m_flDeployHeight - flDeployedHeight ) <= self.BipodDeploy.MaxHeightDifference ) then
2559 self.m_pDeployEntity = pDeployedOn
2560 local vNewPos = pDeployedOn:GetPos()
2561
2562 if ( self.m_vDeployPos ~= vNewPos ) then
2563 self.m_vDeployPos = vNewPos
2564 self.m_flDeployYawLeft = flYawLimitLeft
2565 self.m_flDeployYawRight = flYawLimitRight
2566
2567 if ( bSinglePlayer ) then
2568 net.Start( "GSWeapons-BipodDeploy" )
2569 net.WriteDouble( flYawLimitLeft )
2570 net.WriteDouble( flYawLimitRight )
2571 net.Send( pPlayer )
2572 end
2573 end
2574
2575 return false
2576 end
2577
2578 return true
2579end
2580
2581function SWEP:ToggleDeploy( iIndex )
2582 if ( self:GetDeployed() == 0 ) then
2583 local pPlayer = self:GetOwner()
2584 local bSuccess, flDeployedHeight, pDeployedOn, flYaw, flYawLimitLeft, flYawLimitRight = self:TestDeploy( true )
2585
2586 if ( bSuccess ) then
2587 local flEndTime = CurTime()
2588
2589 self.m_flDeployHeight = flDeployedHeight
2590 self.m_pDeployEntity = pDeployedOn
2591 self.m_vDeployPos = pDeployedOn:GetPos()
2592 self.m_flDeployTime = flEndTime
2593 self.m_flDeployYawStart = flYaw
2594 self.m_flDeployYawLeft = flYawLimitLeft
2595 self.m_flDeployYawRight = flYawLimitRight
2596
2597 // Save this off so we do duck checks later, even though we won't be flagged as ducking
2598 self.m_bDeployCrouched = pPlayer:Crouching()
2599
2600 // More TODO:
2601 // recalc our yaw limits if the item we're deployed on has moved or rotated
2602 // if our new limits are outside our current eye angles, undeploy us
2603
2604 flEndTime = flEndTime + (self:PlayActivity( "deploy", iIndex ) and self:SequenceLength( iIndex ) or 0)
2605 self:SetNextPrimaryFire( flEndTime )
2606 self:SetNextSecondaryFire( flEndTime )
2607 self:SetNextReload( flEndTime )
2608
2609 self:SetDeployed( (iIndex or 0) + 1 )
2610
2611 if ( bSinglePlayer ) then
2612 net.Start( "GSWeapons-BipodDeploy" )
2613 net.WriteDouble( flYaw ) -- No imprecisions
2614 net.WriteDouble( flYawLimitLeft )
2615 net.WriteDouble( flYawLimitRight )
2616 net.Send( pPlayer )
2617 end
2618 end
2619
2620 return bSuccess
2621 end
2622
2623 local flEndTime = CurTime() + (self:PlayActivity( "deploy", iIndex ) and self:SequenceLength( iIndex ) or 0)
2624 self:SetNextPrimaryFire( flEndTime )
2625 self:SetNextSecondaryFire( flEndTime )
2626 self:SetNextReload( flEndTime )
2627
2628 self:SetDeployed(0)
2629
2630 return true
2631end
2632
2633-- Using this instead of Player:MuzzleFlash() allows all viewmodels to use muzzle flash
2634function SWEP:DoMuzzleFlash( iIndex )
2635 if ( not self:Silenced( iIndex )) then
2636 --[[if ( iIndex ) then
2637 -- https://github.com/Facepunch/garrysmod-issues/issues/2552
2638 if ( SERVER ) then
2639 self:SetSaveValue( "m_nMuzzleFlashParity", bit.band( self:GetInternalVariable( "m_nMuzzleFlashParity" ) + 1, 3 ))
2640
2641 local pPlayer = self:GetOwner()
2642 pPlayer:SetSaveValue( "m_nMuzzleFlashParity", bit.band( pPlayer:GetInternalVariable( "m_nMuzzleFlashParity" ) + 1, 3 ))
2643
2644 local pViewModel = pPlayer:GetViewModel( iIndex )
2645
2646 -- Always check if the viewmodel is valid
2647 if ( pViewModel ~= NULL ) then
2648 pViewModel:SetSaveValue( "m_nMuzzleFlashParity", bit.band( pViewModel:GetInternalVariable( "m_nMuzzleFlashParity" ) + 1, 3 ))
2649 end
2650 end
2651 else]]
2652 self:GetOwner():MuzzleFlash()
2653 --end
2654 end
2655end
2656
2657function SWEP:Punch()
2658end
2659
2660function SWEP:HandleFireOnEmpty( bSecondary --[[= false]], iIndex --[[= 0]] )
2661 self:PlaySound( "empty", iIndex )
2662 self:PlayActivity( "empty", iIndex )
2663
2664 local pPlayer = self:GetOwner()
2665
2666 if ( self.EmptyCooldown == -1 ) then
2667 if ( bSecondary ) then
2668 self:SetNextSecondaryFire(-1)
2669
2670 self:AddEvent( "empty_secondary", 0, function()
2671 if ( not pPlayer:KeyDown( IN_ATTACK2 ) or (self.CheckClip1ForSecondary and self:Clip1() or self:Clip2()) ~= 0 ) then
2672 if ( self:GetNextSecondaryFire() == -1 ) then
2673 self:SetNextSecondaryFire(0)
2674 end
2675
2676 return true
2677 end
2678 end )
2679 else
2680 self:SetNextPrimaryFire(-1)
2681
2682 self:AddEvent( "empty_primary", 0, function()
2683 if ( not pPlayer:KeyDown( IN_ATTACK ) or self:Clip1() ~= 0 ) then
2684 if ( self:GetNextPrimaryFire() == -1 ) then
2685 self:SetNextPrimaryFire(0)
2686 end
2687
2688 return true
2689 end
2690 end )
2691 end
2692 else
2693 local flNextTime = CurTime() + self.EmptyCooldown
2694 self:SetNextPrimaryFire( flNextTime )
2695 self:SetNextSecondaryFire( flNextTime )
2696 end
2697
2698 if ( self.SwitchOnEmptyFire and not self:HasAmmo() ) then
2699 pPlayer:SwitchWeapon( pPlayer:GetNextBestWeapon( self.HighWeightPriority ))
2700 elseif ( bSecondary and self.Secondary.ReloadOnEmptyFire or not bSecondary and self.Primary.ReloadOnEmptyFire ) then
2701 self:SetNextReload(0)
2702 self:Reload()
2703 end
2704end
2705
2706function SWEP:HandleFireUnderwater( bSecondary --[[= false]], iIndex --[[= 0]] )
2707 self:PlaySound( "empty", iIndex )
2708 self:PlayActivity( "empty", iIndex )
2709
2710 if ( self.UnderwaterCooldown == -1 ) then
2711 local pPlayer = self:GetOwner()
2712
2713 if ( bSecondary ) then
2714 self:SetNextSecondaryFire(-1)
2715
2716 self:AddEvent( "empty_secondary", 0, function()
2717 if ( not pPlayer:KeyDown( IN_ATTACK2 ) or (self.CheckClip1ForSecondary and self:Clip1() or self:Clip2()) ~= 0 ) then
2718 if ( self:GetNextSecondaryFire() == -1 ) then
2719 self:SetNextSecondaryFire(0)
2720 end
2721
2722 return true
2723 end
2724 end )
2725 else
2726 self:SetNextPrimaryFire(-1)
2727
2728 self:AddEvent( "empty_primary", 0, function()
2729 if ( not pPlayer:KeyDown( IN_ATTACK ) or self:Clip1() ~= 0 ) then
2730 if ( self:GetNextPrimaryFire() == -1 ) then
2731 self:SetNextPrimaryFire(0)
2732 end
2733
2734 return true
2735 end
2736 end )
2737 end
2738 else
2739 local flNextTime = CurTime() + self.UnderwaterCooldown
2740 self:SetNextPrimaryFire( flNextTime )
2741 self:SetNextSecondaryFire( flNextTime )
2742 end
2743end
2744
2745--- Reload
2746function SWEP:CanReload()
2747 local flNextReload = self:GetNextReload()
2748
2749 -- Do not reload if both clips are already full
2750 if ( flNextReload == -1 or flNextReload > CurTime() ) then
2751 return false
2752 end
2753
2754 local pPlayer = self:GetOwner()
2755
2756 if ( pPlayer == NULL ) then
2757 return false
2758 end
2759
2760 // If I don't have any spare ammo, I can't reload
2761 local iMaxClip1 = self:GetMaxClip1()
2762
2763 if ( iMaxClip1 == -1 or self:Clip1() == iMaxClip1 or pPlayer:GetAmmoCount( self:GetPrimaryAmmoName() ) == 0 ) then
2764 local iMaxClip2 = self:GetMaxClip2()
2765
2766 if ( iMaxClip2 == -1 or self:Clip2() == iMaxClip2 or pPlayer:GetAmmoCount( self:GetSecondaryAmmoName() ) == 0 ) then
2767 return false
2768 end
2769 end
2770
2771 return true
2772end
2773
2774-- Will only be called serverside in single-player
2775function SWEP:Reload()
2776 if ( self:CanReload() ) then
2777 self:ReloadClips()
2778
2779 return true
2780 end
2781
2782 return false
2783end
2784
2785-- Not specifying an index will call ShouldReloadViewModel for all viewmodels
2786function SWEP:ReloadClips()
2787 local pPlayer = self:GetOwner()
2788
2789 if ( self.Zoom.UnzoomOnReload and self:GetZoomLevel() ~= 0 ) then
2790 self:SetZoomLevel(0)
2791 pPlayer:SetFOV(0, 0)
2792
2793 if ( self.Zoom.HideViewModel ) then
2794 if ( self.m_tUseViewModel[0] ) then
2795 local pViewModel = pPlayer:GetViewModel(0)
2796 pViewModel:SetVisible( true )
2797 end
2798
2799 if ( self.m_tUseViewModel[1] ) then
2800 local pViewModel = pPlayer:GetViewModel(1)
2801 pViewModel:SetVisible( true )
2802 end
2803
2804 if ( self.m_tUseViewModel[2] ) then
2805 local pViewModel = pPlayer:GetViewModel(2)
2806 pViewModel:SetVisible( true )
2807 end
2808 end
2809 end
2810
2811 local tSingleReload = self.SingleReload
2812
2813 if ( tSingleReload.Enable ) then
2814 local iMaxClip1 = self:GetMaxClip1()
2815 local iMaxClip2 = self:GetMaxClip2()
2816 local sAmmo1
2817 local sAmmo2
2818 local iClip1
2819 local iClip2
2820 local iAmmoCount1
2821 local iAmmoCount2
2822
2823 if ( iMaxClip1 ~= -1 ) then
2824 sAmmo1 = self:GetPrimaryAmmoName()
2825 iClip1 = self:Clip1()
2826 iAmmoCount1 = pPlayer:GetAmmoCount( sAmmo1 )
2827 end
2828
2829 if ( iMaxClip2 ~= -1 ) then
2830 sAmmo2 = self:GetSecondaryAmmoName()
2831 iClip2 = self:Clip2()
2832 iAmmoCount2 = pPlayer:GetAmmoCount( sAmmo2 )
2833 end
2834
2835 // Play the player's reload animation
2836 --pPlayer:SetAnimation( PLAYERANIMEVENT_RELOAD )
2837
2838 local bVM0 = false
2839 local bVM1 = false
2840 local bVM2 = false
2841 local flSequenceDuration = 0
2842 local flFinishTime = 0
2843
2844 if ( self.m_tUseViewModel[0] and self:ShouldReloadViewModel(0) ) then
2845 self:PlaySound( "reload_start", 0 )
2846 bVM0 = true
2847
2848 if ( self:PlayActivity( "reload_start", 0 )) then
2849 flSequenceDuration = self:SequenceLength(0)
2850 end
2851 end
2852
2853 if ( self.m_tUseViewModel[1] and self:ShouldReloadViewModel(1) ) then
2854 self:PlaySound( "reload_start", 1, true )
2855 bVM1 = true
2856
2857 if ( self:PlayActivity( "reload_start", 1 )) then
2858 flSequenceDuration = math.max( flSequenceDuration, self:SequenceLength(1) )
2859 end
2860 end
2861
2862 if ( self.m_tUseViewModel[2] and self:ShouldReloadViewModel(2) ) then
2863 self:PlaySound( "reload_start", 2, true )
2864 bVM2 = true
2865
2866 if ( self:PlayActivity( "reload_start", 2 )) then
2867 flSequenceDuration = math.max( flSequenceDuration, self:SequenceLength(2) )
2868 end
2869 end
2870
2871 self:SetNextReload(-1)
2872 local bFirst = true
2873
2874 self:AddEvent( "reload", flSequenceDuration, function()
2875 -- HACK: Don't reload with primary fire underwater
2876 if ( (pPlayer:KeyDown( IN_ATTACK ) and not self.Primary.FireUnderwater and pPlayer:WaterLevel() == 3
2877 or pPlayer:KeyDown( IN_ATTACK2 )) and not tSingleReload.QueuedFire ) then
2878 self:SetNextIdle(-1)
2879
2880 -- Start reloading when the mouse is lifted
2881 return 0
2882 elseif ( self:GetNextIdle() == -1 ) then
2883 -- Re-enable idling
2884 self:SetNextIdle(0)
2885
2886 -- Skip one tick for PrimaryAttack to have priority
2887 return 0
2888 end
2889
2890 if ( not bFirst or tSingleReload.InitialRound ) then
2891 if ( iMaxClip1 ~= -1 ) then
2892 iClip1 = iClip1 + 1
2893 self:SetClip1( iClip1 )
2894 iAmmoCount1 = iAmmoCount1 - 1
2895 pPlayer:RemoveAmmo( 1, sAmmo1 )
2896
2897 if ( iAmmoCount1 == 0 or iClip1 == iMaxClip1 ) then
2898 iMaxClip1 = -1
2899 end
2900 end
2901
2902 if ( iMaxClip2 ~= -1 ) then
2903 iClip2 = iClip2 + 1
2904 self:SetClip2( iClip2 )
2905 iAmmoCount2 = iAmmoCount2 - 1
2906 pPlayer:RemoveAmmo( 1, sAmmo2 )
2907
2908 if ( iAmmoCount2 == 0 or iClip2 == iMaxClip2 ) then
2909 iMaxClip2 = -1
2910 end
2911 end
2912 end
2913
2914 local flSequenceDuration = 0
2915 local flCurTime = CurTime()
2916
2917 if ( bVM0 ) then
2918 if ( self:ShouldReloadViewModel(0) ) then
2919 self:PlaySound( "reload", 0 )
2920
2921 if ( self:PlayActivity( "reload", 0 )) then
2922 flSequenceDuration = self:SequenceLength(0)
2923 end
2924 else
2925 self:PlaySound( "reload_finish", 0 )
2926 bVM0 = false
2927
2928 if ( self:PlayActivity( "reload_finish", 0 )) then
2929 flFinishTime = math.max( flFinishTime, flCurTime + self:SequenceLength(0) )
2930 end
2931 end
2932 end
2933
2934 if ( bVM1 ) then
2935 if ( self:ShouldReloadViewModel(1) ) then
2936 self:PlaySound( "reload", 1, true )
2937
2938 if ( self:PlayActivity( "reload", 1 )) then
2939 flSequenceDuration = math.max( flSequenceDuration, self:SequenceLength(1) )
2940 end
2941 else
2942 self:PlaySound( "reload_finish", 1, true )
2943 bVM1 = false
2944
2945 if ( self:PlayActivity( "reload_finish", 1 )) then
2946 flFinishTime = math.max( flFinishTime, flCurTime + self:SequenceLength(1) )
2947 end
2948 end
2949 end
2950
2951 if ( bVM2 ) then
2952 if ( self:ShouldReloadViewModel(2) ) then
2953 self:PlaySound( "reload", 2, true )
2954
2955 if ( self:PlayActivity( "reload", 2 )) then
2956 flSequenceDuration = math.max( flSequenceDuration, self:SequenceLength(2) )
2957 end
2958 else
2959 self:PlaySound( "reload_finish", 2, true )
2960 bVM2 = false
2961
2962 if ( self:PlayActivity( "reload_finish", 2 )) then
2963 flFinishTime = math.max( flFinishTime, flCurTime + self:SequenceLength(2) )
2964 end
2965 end
2966 end
2967
2968 if ( iMaxClip1 == -1 and iMaxClip2 == -1 ) then
2969 --pPlayer:DoAnimationEvent( PLAYERANIMEVENT_RELOAD_END )
2970
2971 self:SetNextPrimaryFire( flFinishTime )
2972 self:SetNextSecondaryFire( flFinishTime )
2973 self:SetNextReload( flFinishTime )
2974
2975 self:SetShotsFired(0)
2976 self:FinishReload()
2977
2978 if ( bSinglePlayer ) then
2979 net.Start( "GSWeapons-Finish reload" )
2980 net.Send( pPlayer )
2981 end
2982
2983 return true
2984 end
2985
2986 if ( bFirst ) then
2987 bFirst = false
2988 pPlayer:SetAnimation( PLAYER_RELOAD )
2989 self:SetNextPrimaryFire(0)
2990 self:SetNextSecondaryFire(0)
2991 end
2992
2993 -- Start anim times are different than mid reload
2994 return flSequenceDuration
2995 end )
2996 else
2997 // Play the player's reload animation
2998 pPlayer:SetAnimation( PLAYER_RELOAD )
2999
3000 local flSequenceDuration = 0
3001
3002 if ( self.m_tUseViewModel[0] and self:ShouldReloadViewModel(0) ) then
3003 self:PlaySound( "reload", 0 )
3004
3005 if ( self:PlayActivity( "reload", 0 )) then
3006 flSequenceDuration = self:SequenceLength(0)
3007 end
3008 end
3009
3010 if ( self.m_tUseViewModel[1] and self:ShouldReloadViewModel(1) ) then
3011 self:PlaySound( "reload", 1, true )
3012
3013 if ( self:PlayActivity( "reload", 1 )) then
3014 flSequenceDuration = math.max( flSequenceDuration, self:SequenceLength(1) )
3015 end
3016 end
3017
3018 if ( self.m_tUseViewModel[2] and self:ShouldReloadViewModel(2) ) then
3019 self:PlaySound( "reload", 2, true )
3020
3021 if ( self:PlayActivity( "reload", 2 )) then
3022 flSequenceDuration = math.max( flSequenceDuration, self:SequenceLength(2) )
3023 end
3024 end
3025
3026 self:SetNextReload( CurTime() + flSequenceDuration )
3027
3028 -- Finish reloading after the animation is finished
3029 self:AddEvent( "reload", flSequenceDuration, function()
3030 local iMaxClip = self:GetMaxClip1()
3031
3032 // If I use primary clips, reload primary
3033 if ( iMaxClip ~= -1 ) then
3034 local iClip = self:Clip1()
3035 local sAmmoType = self:GetPrimaryAmmoName()
3036
3037 -- Only reload what is available
3038 local iAmmo = math.min( iMaxClip - iClip, pPlayer:GetAmmoCount( sAmmoType ))
3039
3040 -- Add to the clip
3041 self:SetClip1( iClip + iAmmo )
3042
3043 -- Take from the player's reserve
3044 pPlayer:RemoveAmmo( iAmmo, sAmmoType )
3045 end
3046
3047 iMaxClip = self:GetMaxClip2()
3048
3049 // If I use secondary clips, reload secondary
3050 if ( iMaxClip ~= -1 ) then
3051 local iClip = self:Clip2()
3052 local sAmmoType = self:GetSecondaryAmmoName()
3053 local iAmmo = math.min( iMaxClip - iClip, pPlayer:GetAmmoCount( sAmmoType ))
3054 self:SetClip2( iClip + iAmmo )
3055 pPlayer:RemoveAmmo( iAmmo, sAmmoType )
3056 end
3057
3058 self:SetShotsFired(0)
3059 self:FinishReload()
3060
3061 if ( bSinglePlayer ) then
3062 net.Start( "GSWeapons-Finish reload" )
3063 net.Send( pPlayer )
3064 end
3065
3066 return true
3067 end )
3068 end
3069end
3070
3071function SWEP:FinishReload()
3072end
3073
3074--- Utilities
3075function SWEP:AddEvent( sName, iTime, fCall, bNoPrediction )
3076 local bAddedByName = isstring( sName )
3077
3078 if ( IsFirstTimePredicted() or (bAddedByName and bNoPrediction or fCall == true) ) then
3079 if ( bAddedByName ) then -- Added by name
3080 sName = sName:lower()
3081 self.m_tEvents[sName] = { iTime, CurTime() + iTime, fCall, false }
3082 self.m_tRemovalQueue[sName] = nil -- Fixes edge case of event being added upon removal
3083 else
3084 local iPos = next( self.m_tEventHoles )
3085
3086 if ( iPos ) then
3087 self.m_tEvents[iPos] = { sName, CurTime() + sName, iTime, false }
3088 self.m_tEventHoles[iPos] = nil
3089 else
3090 -- No holes, we can safely use the count operation
3091 self.m_tEvents[#self.m_tEvents] = { sName, CurTime() + sName, iTime, false }
3092 end
3093 end
3094 end
3095end
3096
3097function SWEP:EventActive( sName, bNoPrediction )
3098 sName = sName:lower()
3099
3100 return self.m_tEvents[sName] ~= nil and (bNoPrediction or IsFirstTimePredicted() or self.m_tEvents[sName][4])
3101end
3102
3103function SWEP:RemoveEvent( sName, bNoPrediction )
3104 if ( bNoPrediction ) then
3105 self.m_tEvents[sName:lower()] = nil
3106 else
3107 self.m_tRemovalQueue[sName:lower()] = true
3108 end
3109end
3110
3111function SWEP:AddNWVar( sType, sName, bAddFunctions --[[= true]], DefaultVal --[[= nil]] )
3112 -- Handle the table here in-case initialize runs after on the client
3113 if ( not self.m_tNWVarSlots ) then
3114 self.m_tNWVarSlots = {}
3115 end
3116
3117 local iSlot = self.m_tNWVarSlots[sType] or 0
3118 self.m_tNWVarSlots[sType] = iSlot + 1
3119
3120 self:DTVar( sType, iSlot, sName )
3121
3122 if ( bAddFunctions or bAddFunctions == nil ) then
3123 self["Get" .. sName] = function( self ) return self.dt[sName] end
3124 self["Set" .. sName] = function( self, Val ) self.dt[sName] = Val end
3125 end
3126
3127 if ( DefaultVal ) then
3128 self.dt[sName] = DefaultVal
3129 end
3130end
3131
3132-- Will only be called serverside in single-player
3133function SWEP:DoImpactEffect()
3134 return false
3135end
3136
3137function SWEP:DoSplashEffect()
3138 return false
3139end
3140
3141function SWEP:PlaySound( sSound, iIndex, bStrictIndex, bPlayShared --[[= false]] )
3142 -- Not a sound file or sound scape
3143 if ( not (string.IsSoundFile( sSound ) or sSound:find( '.', 2, true ))) then
3144 local sPrefix = self:GetSoundPrefix( sSound, iIndex )
3145
3146 if ( sPrefix == "" ) then
3147 sSound = self:LookupSound( sSound, iIndex, bStrictIndex )
3148
3149 if ( sSound == "" ) then
3150 return false
3151 end
3152 else
3153 local sPlay = self:LookupSound( string.format( sFormatOne, sPrefix, sSound ), iIndex, bStrictIndex )
3154
3155 if ( sPlay == "" ) then
3156 sSound = self:LookupSound( sSound, iIndex, bStrictIndex )
3157
3158 if ( sSound == "" ) then
3159 return false
3160 end
3161 else
3162 sSound = sPlay
3163 end
3164 end
3165 end
3166
3167 if ( bPlayShared or SERVER ) then
3168 local pPlayer = self:GetOwner()
3169
3170 if ( pPlayer == NULL ) then
3171 self:EmitSound( sSound )
3172 else
3173 pPlayer:EmitSound( sSound )
3174 end
3175 end
3176
3177 return true
3178end
3179
3180local tDeployActivities = {
3181 [ACT_VM_DRAW] = true,
3182 [ACT_VM_DRAW_SILENCED] = true,
3183 [ACT_VM_DRAW_DEPLOYED] = true,
3184 [ACT_VM_DRAW_EMPTY] = true,
3185 draw = true
3186}
3187
3188-- SendWeaponAnim that supports idle times and multiple view models
3189-- Sending in a value for flRate will ignore the SWEP.Activities defined rate
3190function SWEP:PlayActivity( Activity, iIndex --[[= 0]], flRate --[[= 1]], bStrictPrefix --[[= false]], bStrictSuffix --[[= false]] )
3191 if ( not iIndex ) then
3192 iIndex = 0
3193 end
3194
3195 -- Do not play an animation if the weapon is invisible
3196 if ( not self:IsVisible( iIndex )) then
3197 return false
3198 end
3199
3200 local pPlayer = self:GetOwner()
3201 local pViewModel = pPlayer:GetViewModel( iIndex )
3202
3203 -- Do not give animations to something that does not exist or is invisible
3204 if ( pViewModel == NULL ) then
3205 return false
3206 end
3207
3208 local bSequence = true
3209 local iSequence, sActivity, iActivity
3210
3211 -- HACK: Weapons with a very short idle animation (CS:S weapons) were causing some efficiency issues
3212 -- With how many table lookups/comparisons were being done to find an activity
3213 -- This code is very messy but the most efficient way of doing it while still supporting tags
3214 if ( isstring( Activity )) then
3215 local sPrefix = self:GetActivityPrefix( Activity, iIndex )
3216 local sSuffix = self:GetActivitySuffix( Activity, iIndex )
3217 local bPrefix = sPrefix ~= ""
3218 local bSuffix = sSuffix ~= ""
3219
3220 if ( bPrefix and bSuffix ) then
3221 sActivity = string.format( sFormatTwo, sPrefix, Activity, sSuffix )
3222 iActivity = self:LookupActivity( sActivity, iIndex )
3223
3224 if ( isstring( iActivity )) then
3225 self:PlaySequence( iActivity, iIndex, flRate )
3226
3227 -- No way to tell if it succeeded or not
3228 return true
3229 end
3230
3231 if ( iActivity == ACT_RESET ) then
3232 return self:ResetActivity( iIndex, self:LookupActivityKey( sActivity, "rate", iIndex ))
3233 end
3234
3235 iSequence = iActivity == ACT_INVALID and -1 or pViewModel:SelectWeightedSequence( iActivity )
3236 bSequence = iSequence == -1
3237 end
3238
3239 if ( bSequence ) then
3240 if ( bPrefix and not (bStrictSuffix and bSuffix) ) then
3241 sActivity = string.format( sFormatOne, sPrefix, Activity )
3242 iActivity = self:LookupActivity( sActivity, iIndex )
3243
3244 if ( isstring( iActivity )) then
3245 self:PlaySequence( iActivity, iIndex, flRate )
3246
3247 return true
3248 end
3249
3250 if ( iActivity == ACT_RESET ) then
3251 return self:ResetActivity( iIndex, self:LookupActivityKey( sActivity, "rate", iIndex ))
3252 end
3253
3254 iSequence = iActivity == ACT_INVALID and -1 or pViewModel:SelectWeightedSequence( iActivity )
3255 bSequence = iSequence == -1
3256 end
3257
3258 if ( bSequence ) then
3259 if ( bPrefix and bStrictPrefix ) then
3260 return false
3261 end
3262
3263 if ( bSuffix ) then
3264 sActivity = string.format( sFormatOne, Activity, sSuffix )
3265 iActivity = self:LookupActivity( sActivity, iIndex )
3266
3267 if ( isstring( iActivity )) then
3268 self:PlaySequence( iActivity, iIndex, flRate )
3269
3270 return true
3271 end
3272
3273 if ( iActivity == ACT_RESET ) then
3274 return self:ResetActivity( iIndex, self:LookupActivityKey( sActivity, "rate", iIndex ))
3275 end
3276
3277 iSequence = iActivity == ACT_INVALID and -1 or pViewModel:SelectWeightedSequence( iActivity )
3278 bSequence = iSequence == -1
3279 end
3280
3281 if ( bSequence ) then
3282 if ( bSuffix and bStrictSuffix ) then
3283 return false
3284 end
3285
3286 sActivity = Activity
3287 iActivity = self:LookupActivity( sActivity, iIndex )
3288
3289 if ( isstring( iActivity )) then
3290 self:PlaySequence( iActivity, iIndex, flRate )
3291
3292 return true
3293 end
3294
3295 if ( iActivity == ACT_RESET ) then
3296 return self:ResetActivity( iIndex, self:LookupActivityKey( sActivity, "rate", iIndex ))
3297 end
3298
3299 iSequence = iActivity == ACT_INVALID and -1 or pViewModel:SelectWeightedSequence( iActivity )
3300 bSequence = iSequence == -1
3301 end
3302 end
3303 end
3304
3305 if ( not flRate ) then
3306 flRate = self:LookupActivityKey( sActivity, "rate", iIndex ) or 1
3307 end
3308 else
3309 if ( Activity == ACT_INVALID ) then
3310 return false
3311 end
3312
3313 if ( Activity == ACT_RESET ) then
3314 return self:ResetActivity( iIndex, flRate )
3315 end
3316
3317 if ( not flRate ) then
3318 flRate = 1
3319 end
3320
3321 iActivity = Activity
3322 iSequence = pViewModel:SelectWeightedSequence( Activity )
3323 bSequence = iSequence == -1
3324 end
3325
3326 if ( bSequence ) then
3327 return false
3328 end
3329
3330 if ( iIndex == 0 ) then
3331 // Take the new activity
3332 self:SetSaveValue( "m_IdealActivity", iActivity )
3333 self:SetSaveValue( "m_nIdealSequence", iSequence )
3334 end
3335
3336 // Don't use transitions when we're deploying
3337 if ( not tDeployActivities[Activity] ) then
3338 // Find the next sequence in the potential chain of sequences leading to our ideal one
3339 local iNext = pViewModel:FindTransitionSequence( pViewModel:GetSequence(), iSequence )
3340
3341 if ( iNext ~= iSequence ) then
3342 // Set our activity to the next transitional animation
3343 iActivity = ACT_TRANSITION
3344 iSequence = iNext
3345 end
3346 end
3347
3348 if ( iIndex == 0 ) then
3349 -- Since m_Activity in not avaliable in the save table, run Weapon:Weapon_SetActivity() and override everything afterward
3350 self:Weapon_SetActivity( iActivity )
3351 self:SetSequence( iSequence )
3352 self:SetPlaybackRate( flRate )
3353 end
3354
3355 if ( SERVER or self:GetPredictable() ) then
3356 -- Enable the view-model if an animation is sent to it
3357 --pViewModel:SetWeaponModel( self:GetViewModel( iIndex ), self )
3358 pViewModel:SendViewModelMatchingSequence( iSequence )
3359 pViewModel:SetPlaybackRate( flRate )
3360 end
3361
3362 if ( flRate == 0 ) then -- Invalid rate; reset idle time
3363 if ( iIndex == 0 ) then
3364 self:SetNextIdle(0)
3365 elseif ( iIndex == 1 ) then
3366 self:SetNextIdle1(0)
3367 elseif ( iIndex == 2 ) then
3368 self:SetNextIdle2(0)
3369 end
3370 else
3371 local flTime = self:LookupActivityKey( sActivity, "idle", iIndex ) or pViewModel:SequenceDuration()
3372
3373 if ( istable( flTime )) then
3374 gsrand:SetSeed( pPlayer:GetMD5Seed() % 0x100 )
3375 flTime = gsrand:RandomFloat( flTime[1], flTime[2] )
3376 end
3377
3378 if ( iIndex == 0 ) then
3379 self:SetNextIdle( flTime / (flRate < 0 and -flRate or flRate) + CurTime() )
3380 elseif ( iIndex == 1 ) then
3381 self:SetNextIdle1( flTime / (flRate < 0 and -flRate or flRate) + CurTime() )
3382 elseif ( iIndex == 2 ) then
3383 self:SetNextIdle2( flTime / (flRate < 0 and -flRate or flRate) + CurTime() )
3384 end
3385 end
3386
3387 return true
3388end
3389
3390function SWEP:PlaySequence( Sequence, iIndex --[[= 0]], flRate --[[= 1]] )
3391 if ( Sequence == -1 ) then
3392 return false
3393 end
3394
3395 local pViewModel = self:GetOwner():GetViewModel( iIndex )
3396
3397 if ( pViewModel == NULL ) then
3398 return false
3399 end
3400
3401 local flTime
3402
3403 if ( isstring( Sequence )) then
3404 Sequence, flTime = pViewModel:LookupSequence( Sequence )
3405
3406 if ( Sequence == -1 ) then
3407 return false
3408 end
3409 else
3410 flTime = pViewModel:SequenceDuration( Sequence )
3411 end
3412
3413 flRate = flRate or 1
3414
3415 if ( SERVER or self:GetPredictable() ) then
3416 --pViewModel:SetWeaponModel( self:GetViewModel( iIndex ), self )
3417 pViewModel:SendViewModelMatchingSequence( Sequence )
3418 pViewModel:SetPlaybackRate( flRate )
3419 end
3420
3421 if ( flRate == 0 ) then
3422 if ( iIndex == 0 ) then
3423 self:SetNextIdle(0)
3424 elseif ( iIndex == 1 ) then
3425 self:SetNextIdle1(0)
3426 elseif ( iIndex == 2 ) then
3427 self:SetNextIdle2(0)
3428 end
3429 else
3430 if ( iIndex == 0 ) then
3431 self:SetNextIdle( flTime / (flRate < 0 and -flRate or flRate) + CurTime() )
3432 elseif ( iIndex == 1 ) then
3433 self:SetNextIdle1( flTime / (flRate < 0 and -flRate or flRate) + CurTime() )
3434 elseif ( iIndex == 2 ) then
3435 self:SetNextIdle2( flTime / (flRate < 0 and -flRate or flRate) + CurTime() )
3436 end
3437 end
3438end
3439
3440function SWEP:ResetActivity( iIndex --[[= 0]], flRate --[[= nil]] )
3441 local pViewModel = self:GetOwner():GetViewModel( iIndex )
3442
3443 if ( pViewModel == NULL ) then
3444 return false
3445 end
3446
3447 pViewModel:SetCycle(0)
3448
3449 if ( flRate ) then
3450 pViewModel:SetPlaybackRate( flRate )
3451 else
3452 flRate = pViewModel:GetPlaybackRate()
3453 end
3454
3455 if ( flRate == 0 ) then
3456 if ( iIndex == 0 ) then
3457 self:SetNextIdle(0)
3458 elseif ( iIndex == 1 ) then
3459 self:SetNextIdle1(0)
3460 elseif ( iIndex == 2 ) then
3461 self:SetNextIdle2(0)
3462 end
3463 else
3464 if ( iIndex == 0 ) then
3465 self:SetNextIdle( pViewModel:SequenceDuration() / flRate + CurTime() )
3466 elseif ( iIndex == 1 ) then
3467 self:SetNextIdle1( pViewModel:SequenceDuration() / flRate + CurTime() )
3468 elseif ( iIndex == 2 ) then
3469 self:SetNextIdle2( pViewModel:SequenceDuration() / flRate + CurTime() )
3470 end
3471 end
3472
3473 return true
3474end
3475-- FIXME: Add prediction count system with UnpredictedCurTime
3476function SWEP:SetupPredictedVar( sName, Initial )
3477 self[sName] = Initial
3478 self[sName .. "-PREV"] = Initial
3479 self[sName .. "-TIME"] = 0
3480end
3481
3482function SWEP:SetPredictedVar( sName, Val )
3483 self[sName .. "-PREV"] = self[sName]
3484 self[sName] = Val
3485 self[sName .. "-TIME"] = CurTime()
3486end
3487
3488function SWEP:GetPredictedVar( sName, bNoPrediction )
3489 if ( bNoPrediction or IsFirstTimePredicted() or self[sName .. "-TIME"] ~= CurTime() ) then
3490 return self[sName]
3491 end
3492
3493 return self[sName .. "-PREV"]
3494end
3495
3496function SWEP:TranslateActivity( iAct )
3497 return self.m_tActivityTranslate[iAct] or -1
3498end
3499
3500-- Used by PlayActivity to detect when to add the _empty tag
3501function SWEP:ViewModelEmpty( iIndex --[[= 0]] )
3502 if ( not iIndex or iIndex == 0 ) then
3503 return self:GetShootClip( self:SpecialActive( iIndex )) == 0
3504 end
3505
3506 return false
3507end
3508
3509-- Used by Reload to know when to play the reload anim
3510function SWEP:ShouldReloadViewModel( iIndex --[[= 0]] )
3511 if ( not iIndex or iIndex == 0 ) then
3512 return self:Clip1() ~= self:GetMaxClip1()
3513 end
3514
3515 return false
3516end
3517
3518--- Accessors/Modifiers
3519-- The functions commented out are already in the weapon metatable
3520--[[function SWEP:AllowsAutoSwtichFrom()
3521 return self.AutoSwitchFrom
3522end
3523
3524function SWEP:AllowsAutoSwitchTo()
3525 return self.AutoSwitchTo
3526end]]
3527-- FIXME: Add function return
3528function SWEP:GetSpecialKey( sKey, bSecondary, bNoConVar )
3529 local Val
3530
3531 if ( bSecondary ) then
3532 Val = self.Secondary[sKey]
3533
3534 if ( Val ~= -1 ) then
3535 return not bNoConVar and isstring( Val ) and GetConVarNumber( Val ) or Val
3536 end
3537 end
3538
3539 Val = self.Primary[sKey]
3540
3541 return not bNoConVar and isstring( Val ) and GetConVarNumber( Val ) or Val
3542end
3543
3544function SWEP:BurstEnabled( iIndex )
3545 if ( iIndex ) then
3546 return bit.band( self:GetBurst(), 2^iIndex ) ~= 0
3547 end
3548
3549 return self:GetBurst() ~= 0
3550end
3551
3552function SWEP:SetWeaponHoldType( sHold )
3553 sHold = sHold:lower()
3554 self.HoldType = sHold
3555 self.m_tActivityTranslate = gsweapons.GetHoldType( sHold )
3556end
3557
3558function SWEP:FlipsViewModel( iIndex --[[= 0]] )
3559 return iIndex == 0 and self.ViewModel or iIndex == 1 and self.ViewModelFlip1 or iIndex == 2 and self.ViewModelFlip2 or ""
3560end
3561
3562function SWEP:GetActivityPrefix( sActivity, iIndex )
3563 if ( self:Silenced( iIndex )) then
3564 return "s"
3565 end
3566
3567 if ( self:GetDeployed() == iIndex + 1 ) then
3568 return "d"
3569 end
3570
3571 return ""
3572end
3573
3574-- Games like DoD:S have team-based skins
3575-- Override this return to choose what skin to use for each viewmodel
3576function SWEP:GetViewModelSkin( iIndex )
3577 return 0
3578end
3579
3580function SWEP:GetActivitySuffix( sActivity, iIndex )
3581 if ( sActivity ~= "empty" and self:ViewModelEmpty( iIndex )) then
3582 return "empty"
3583 end
3584
3585 return ""
3586end
3587
3588function SWEP:GetSoundPrefix( sSound, iIndex )
3589 if ( self:Silenced( iIndex )) then
3590 return "s"
3591 end
3592
3593 return ""
3594end
3595
3596function SWEP:GetDefaultClip1()
3597 return self.Primary.DefaultClip
3598end
3599
3600function SWEP:GetDefaultClip2()
3601 return self.Secondary.DefaultClip
3602end
3603
3604-- FIXME: Add silenced conditions to this
3605function SWEP:GetDryfireActivity( iIndex --[[= 0]] )
3606 if ( not iIndex or iIndex == 0 ) then
3607 return "shoot_empty"
3608 end
3609
3610 return ""
3611end
3612
3613--[[function SWEP:GetMaxClip1()
3614 return self.Primary.ClipSize
3615end
3616
3617function SWEP:GetMaxClip2()
3618 return self.Secondary.ClipSize
3619end]]
3620
3621function SWEP:GetMuzzleAttachment( iEvent )
3622 -- Assume first attachment
3623 return iEvent and (iEvent - 4991) / 10 or 1
3624end
3625
3626function SWEP:GetEjectionAttachment( iEvent )
3627 return 2
3628end
3629
3630--[[function SWEP:GetOwner()
3631 return self.Owner -- Will always be an entity (NULL included)
3632end
3633
3634function SWEP:GetPrimaryAmmoType()
3635 return game.GetAmmoID( self.Primary.Ammo )
3636end
3637
3638function SWEP:GetSecondaryAmmoType()
3639 return game.GetAmmoID( self.Secondary.Ammo )
3640end]]
3641
3642function SWEP:GetPrimaryAmmoName()
3643 return self.Primary.Ammo
3644end
3645
3646function SWEP:GetSecondaryAmmoName()
3647 return self.Secondary.Ammo
3648end
3649
3650function SWEP:GetGrenadeAmmoName( iIndex )
3651 return self:GetPrimaryAmmoName()
3652end
3653
3654--[[function SWEP:GetPrintName()
3655 return self.PrintName
3656end]]
3657
3658function SWEP:GetShootAngles()
3659 local pPlayer = self:GetOwner()
3660
3661 return pPlayer:EyeAngles() + pPlayer:GetViewPunchAngles()
3662end
3663
3664function SWEP:GetShootSrc()
3665 return self:GetOwner():GetShootPos()
3666end
3667
3668function SWEP:GetShotTable( bSecondary )
3669 return {
3670 AmmoType = bSecondary and not self.CheckClip1ForSecondary
3671 and self:GetSecondaryAmmoName() or self:GetPrimaryAmmoName(),
3672 Damage = self:GetSpecialKey( "Cooldown", bSecondary ),
3673 Dir = self:GetShootAngles( bSecondary ):Forward(),
3674 Distance = self:GetSpecialKey( "Range", bSecondary ),
3675 --Flags = FIRE_BULLETS_ALLOW_WATER_SURFACE_IMPACTS,
3676 Force = self:GetSpecialKey( "Force", bSecondary ),
3677 Num = self:GetSpecialKey( "Bullets", bSecondary ),
3678 Spread = self:GetSpecialKey( "Spread", bSecondary ),
3679 SpreadBias = self:GetSpecialKey( "SpreadBias", bSecondary ),
3680 Src = self:GetShootSrc( bSecondary ),
3681 Tracer = self:GetSpecialKey( "TracerFreq", bSecondary ),
3682 TracerName = self:GetSpecialKey( "TracerName", bSecondary, true )
3683 }
3684end
3685
3686--[[function SWEP:GetSlot()
3687 return self.Slot
3688end
3689
3690function SWEP:GetSlotPos()
3691 return self.SlotPos
3692end]]
3693
3694function SWEP:GetViewModel( iIndex --[[= 0]] )
3695 return iIndex == 1 and self.ViewModel1 or iIndex == 2 and self.ViewModel2 or self.ViewModel
3696end
3697
3698--[[function SWEP:GetWeaponViewModel()
3699 return self.ViewModel
3700end
3701
3702function SWEP:GetWeaponWorldModel()
3703 return self.WorldModel
3704end
3705
3706function SWEP:GetWeight()
3707 return self.Weight
3708end]]
3709
3710function SWEP:IronSightsEnabled( iIndex )
3711 if ( iIndex ) then
3712 return bit.band( self:GetIronSights(), 2^iIndex ) ~= 0
3713 end
3714
3715 return self:GetIronSights() ~= 0
3716end
3717
3718-- Returns the activity and playback rate
3719function SWEP:LookupActivity( sName, iIndex --[[= 0]] )
3720 local Activity = self.Activities[sName]
3721
3722 if ( Activity ) then
3723 if ( istable( Activity )) then
3724 Activity = iIndex and Activity[iIndex + 1] or Activity[1]
3725
3726 if ( Activity ) then
3727 -- FIXME: Add method to see if the seed has been set this command
3728 gsrand:SetSeed( self:GetOwner():GetMD5Seed() % 0x100 )
3729
3730 return istable( Activity ) and Activity[gsrand:RandomInt( 1, #Activity )] or Activity or ACT_INVALID
3731 end
3732
3733 return ACT_INVALID
3734 end
3735
3736 -- Enum or sequence
3737 return Activity
3738 end
3739
3740 return ACT_INVALID
3741end
3742
3743function SWEP:LookupActivityKey( sName, sKey, iIndex )
3744 local Activity = self.Activities[sName]
3745
3746 if ( Activity and istable( Activity )) then
3747 if ( Activity[sKey .. iIndex + 1] == nil ) then
3748 return Activity[sKey]
3749 end
3750
3751 return Activity[sKey .. iIndex + 1]
3752 end
3753end
3754
3755function SWEP:LookupSound( sName, iIndex --[[= 0]], bStrictIndex --[[= false]] )
3756 local sSound = self.Sounds[sName]
3757
3758 if ( sSound ) then
3759 -- Auto-refresh fix
3760 if ( istable( sSound ) or string.IsSoundFile( sSound )) then
3761 self:Precache()
3762
3763 sSound = self.Sounds[sName]
3764
3765 if ( not sSound ) then
3766 return ""
3767 end
3768 end
3769
3770 if ( not iIndex or iIndex == 0 or bStrictIndex ) then
3771 return string.format( sSound, iIndex or 0 )
3772 end
3773
3774 local sFormatSound = string.format( sSound, iIndex )
3775
3776 if ( sound.GetProperties( sFormatSound )) then
3777 return sFormatSound
3778 end
3779
3780 return string.format( sSound, 0 )
3781 end
3782
3783 return ""
3784end
3785
3786function SWEP:Silenced( iIndex --[[= nil]] )
3787 if ( iIndex ) then
3788 return bit.band( self:GetSilenced(), 2^iIndex ) ~= 0
3789 end
3790
3791 return self:GetSilenced() ~= 0
3792end
3793
3794-- The player is considered to be in-zoom to variable modifiers if they are fully zoomed
3795-- This prevents quick-scoping for the spread/damage/cooldown benfits
3796function SWEP:SpecialActive( iIndex --[[= nil]] )
3797 if ( self:Silenced( iIndex ) or self:BurstEnabled( iIndex ) or self:IronSightsEnabled( iIndex )) then
3798 return true
3799 end
3800
3801 if ( iIndex ) then
3802 if ( self:GetDeployed() == iIndex + 1 ) then
3803 return true
3804 end
3805 elseif ( self:GetDeployed() ~= 0 ) then
3806 return true
3807 end
3808
3809 if ( self:GetZoomLevel() > 0 ) then
3810 if ( not iIndex ) then
3811 local flCurTime = CurTime()
3812
3813 return self:GetZoomActiveTime() <= flCurTime and self:GetZoomActiveTime1() <= flCurTime and self:GetZoomActiveTime2() <= flCurTime
3814 end
3815
3816 if ( iIndex == 0 ) then
3817 return self:GetZoomActiveTime() <= CurTime()
3818 end
3819
3820 if ( iIndex == 1 ) then
3821 return self:GetZoomActiveTime1() <= CurTime()
3822 end
3823
3824 if ( iIndex == 2 ) then
3825 return self:GetZoomActiveTime2() <= CurTime()
3826 end
3827 end
3828
3829 return false
3830end
3831
3832function SWEP:UsesHands()
3833 return self.UseHands
3834end