· 8 years ago · Aug 12, 2018, 01:56 AM
1-- Custom weapon base, used to derive from CS one, still very similar
2
3AddCSLuaFile()
4
5---- TTT SPECIAL EQUIPMENT FIELDS
6
7-- This must be set to one of the WEAPON_ types in TTT weapons for weapon
8-- carrying limits to work properly. See /gamemode/shared.lua for all possible
9-- weapon categories.
10SWEP.Kind = WEAPON_NONE
11
12-- If CanBuy is a table that contains ROLE_TRAITOR and/or ROLE_DETECTIVE, those
13-- players are allowed to purchase it and it will appear in their Equipment Menu
14-- for that purpose. If CanBuy is nil this weapon cannot be bought.
15-- Example: SWEP.CanBuy = { ROLE_TRAITOR }
16-- (just setting to nil here to document its existence, don't make this buyable)
17SWEP.CanBuy = nil
18
19if CLIENT then
20 -- If this is a buyable weapon (ie. CanBuy is not nil) EquipMenuData must be
21 -- a table containing some information to show in the Equipment Menu. See
22 -- default equipment weapons for real-world examples.
23 SWEP.EquipMenuData = nil
24
25 -- Example data:
26 -- SWEP.EquipMenuData = {
27 --
28 ---- Type tells players if it's a weapon or item
29 -- type = "Weapon",
30 --
31 ---- Desc is the description in the menu. Needs manual linebreaks (via \n).
32 -- desc = "Text."
33 -- };
34
35 -- This sets the icon shown for the weapon in the DNA sampler, search window,
36 -- equipment menu (if buyable), etc.
37 SWEP.Icon = "vgui/ttt/icon_nades" -- most generic icon I guess
38
39 -- You can make your own weapon icon using the template in:
40 -- /garrysmod/gamemodes/terrortown/template/
41
42 -- Open one of TTT's icons with VTFEdit to see what kind of settings to use
43 -- when exporting to VTF. Once you have a VTF and VMT, you can
44 -- resource.AddFile("materials/vgui/...") them here. GIVE YOUR ICON A UNIQUE
45 -- FILENAME, or it WILL be overwritten by other servers! Gmod does not check
46 -- if the files are different, it only looks at the name. I recommend you
47 -- create your own directory so that this does not happen,
48 -- eg. /materials/vgui/ttt/mycoolserver/mygun.vmt
49end
50
51---- MISC TTT-SPECIFIC BEHAVIOUR CONFIGURATION
52
53-- ALL weapons in TTT must have weapon_tttbase as their SWEP.Base. It provides
54-- some functions that TTT expects, and you will get errors without them.
55-- Of course this is weapon_tttbase itself, so I comment this out here.
56-- SWEP.Base = "weapon_tttbase"
57
58-- If true AND SWEP.Kind is not WEAPON_EQUIP, then this gun can be spawned as
59-- random weapon by a ttt_random_weapon entity.
60SWEP.AutoSpawnable = false
61
62-- Set to true if weapon can be manually dropped by players (with Q)
63SWEP.AllowDrop = true
64
65-- Set to true if weapon kills silently (no death scream)
66SWEP.IsSilent = false
67
68-- If this weapon should be given to players upon spawning, set a table of the
69-- roles this should happen for here
70-- SWEP.InLoadoutFor = { ROLE_TRAITOR, ROLE_DETECTIVE, ROLE_INNOCENT }
71
72-- DO NOT set SWEP.WeaponID. Only the standard TTT weapons can have it. Custom
73-- SWEPs do not need it for anything.
74-- SWEP.WeaponID = nil
75
76---- YE OLDE SWEP STUFF
77
78if CLIENT then
79 SWEP.DrawCrosshair = false
80 SWEP.ViewModelFOV = 82
81 SWEP.ViewModelFlip = true
82 SWEP.CSMuzzleFlashes = true
83end
84
85SWEP.Base = "weapon_base"
86
87SWEP.Category = "TTT"
88SWEP.Spawnable = false
89
90SWEP.IsGrenade = false
91
92SWEP.Weight = 5
93SWEP.AutoSwitchTo = false
94SWEP.AutoSwitchFrom = false
95
96SWEP.Primary.Sound = Sound( "Weapon_Pistol.Empty" )
97SWEP.Primary.Recoil = 1.5
98SWEP.Primary.Damage = 1
99SWEP.Primary.NumShots = 1
100SWEP.Primary.Cone = 0.02
101SWEP.Primary.Delay = 0.15
102
103SWEP.Primary.ClipSize = -1
104SWEP.Primary.DefaultClip = SWEP.Primary.ClipSize * 9999
105SWEP.Primary.Automatic = false
106SWEP.Primary.Ammo = "none"
107SWEP.Primary.ClipMax = -1
108
109SWEP.Secondary.ClipSize = 1
110SWEP.Secondary.DefaultClip = 1
111SWEP.Secondary.Automatic = false
112SWEP.Secondary.Ammo = "none"
113SWEP.Secondary.ClipMax = -1
114
115SWEP.HeadshotMultiplier = 2.7
116
117SWEP.StoredAmmo = 0
118SWEP.IsDropped = false
119
120SWEP.DeploySpeed = 1.4
121
122SWEP.PrimaryAnim = ACT_VM_PRIMARYATTACK
123SWEP.ReloadAnim = ACT_VM_RELOAD
124
125SWEP.Reloading = false
126
127SWEP.fingerprints = {}
128
129local sparkle = CLIENT and CreateConVar("ttt_crazy_sparks", "0", FCVAR_ARCHIVE)
130
131-- crosshair
132if CLIENT then
133 local sights_opacity = CreateConVar("ttt_ironsights_crosshair_opacity", "0.8", FCVAR_ARCHIVE)
134 local crosshair_brightness = CreateConVar("ttt_crosshair_brightness", "1.0", FCVAR_ARCHIVE)
135 local crosshair_size = CreateConVar("ttt_crosshair_size", "1.0", FCVAR_ARCHIVE)
136 local disable_crosshair = CreateConVar("ttt_disable_crosshair", "0", FCVAR_ARCHIVE)
137
138 function SWEP:DrawHUD()
139 if self.HUDHelp then
140 self:DrawHelp()
141 end
142
143 local client = LocalPlayer()
144 if disable_crosshair:GetBool() or (not IsValid(client)) then return end
145
146 local sights = (not self.NoSights) and self:GetIronsights()
147
148 local x = math.floor(ScrW() / 2.0)
149 local y = math.floor(ScrH() / 2.0)
150 local scale = math.max(0.2, 10 * self:GetPrimaryCone())
151
152 local LastShootTime = self:LastShootTime()
153 scale = scale * (2 - math.Clamp( (CurTime() - LastShootTime) * 5, 0.0, 1.0 ))
154
155 local alpha = sights and sights_opacity:GetFloat() or 1
156 local bright = crosshair_brightness:GetFloat() or 1
157
158 -- somehow it seems this can be called before my player metatable
159 -- additions have loaded
160 if client.IsTraitor and client:IsTraitor() then
161 surface.SetDrawColor(255 * bright,
162 50 * bright,
163 50 * bright,
164 255 * alpha)
165 else
166 surface.SetDrawColor(0,
167 255 * bright,
168 0,
169 255 * alpha)
170 end
171
172 local gap = math.floor(20 * scale * (sights and 0.8 or 1))
173 local length = math.floor(gap + (25 * crosshair_size:GetFloat()) * scale)
174 surface.DrawLine( x - length, y, x - gap, y )
175 surface.DrawLine( x + length, y, x + gap, y )
176 surface.DrawLine( x, y - length, x, y - gap )
177 surface.DrawLine( x, y + length, x, y + gap )
178 end
179
180 local GetPTranslation = LANG.GetParamTranslation
181
182 -- Many non-gun weapons benefit from some help
183 local help_spec = {text = "", font = "TabLarge", xalign = TEXT_ALIGN_CENTER}
184 function SWEP:DrawHelp()
185 local data = self.HUDHelp
186
187 local translate = data.translatable
188 local primary = data.primary
189 local secondary = data.secondary
190
191 if translate then
192 primary = primary and GetPTranslation(primary, data.translate_params)
193 secondary = secondary and GetPTranslation(secondary, data.translate_params)
194 end
195
196 help_spec.pos = {ScrW() / 2.0, ScrH() - 40}
197 help_spec.text = secondary or primary
198 draw.TextShadow(help_spec, 2)
199
200 -- if no secondary exists, primary is drawn at the bottom and no top line
201 -- is drawn
202 if secondary then
203 help_spec.pos[2] = ScrH() - 60
204 help_spec.text = primary
205 draw.TextShadow(help_spec, 2)
206 end
207 end
208
209 -- mousebuttons are enough for most weapons
210 local default_key_params = {
211 primaryfire = Key("+attack", "LEFT MOUSE"),
212 secondaryfire = Key("+attack2", "RIGHT MOUSE"),
213 usekey = Key("+use", "USE")
214 };
215
216 function SWEP:AddHUDHelp(primary_text, secondary_text, translate, extra_params)
217 extra_params = extra_params or {}
218
219 self.HUDHelp = {
220 primary = primary_text,
221 secondary = secondary_text,
222 translatable = translate,
223 translate_params = table.Merge(extra_params, default_key_params)
224 };
225 end
226end
227
228-- Shooting functions largely copied from weapon_cs_base
229function SWEP:PrimaryAttack(worldsnd)
230
231 self:SetNextSecondaryFire( CurTime() + self.Primary.Delay )
232 self:SetNextPrimaryFire( CurTime() + self.Primary.Delay )
233
234 if not self:CanPrimaryAttack() then return end
235
236 if not worldsnd then
237 self:EmitSound( self.Primary.Sound, self.Primary.SoundLevel )
238 elseif SERVER then
239 sound.Play(self.Primary.Sound, self:GetPos(), self.Primary.SoundLevel)
240 end
241
242 self:ShootBullet( self.Primary.Damage, self.Primary.Recoil, self.Primary.NumShots, self:GetPrimaryCone() )
243
244 self:TakePrimaryAmmo( 1 )
245
246 local owner = self:GetOwner()
247 if not IsValid(owner) or owner:IsNPC() or (not owner.ViewPunch) then return end
248
249 owner:ViewPunch( Angle( util.SharedRandom(self:GetClass(),-0.2,-0.1,0) * self.Primary.Recoil, util.SharedRandom(self:GetClass(),-0.1,0.1,1) * self.Primary.Recoil, 0 ) )
250
251
252 print("self.Clip1 = ", self:Clip1())
253 if (self:Clip1() <= 0 ) then
254 print("Ammo exhausted. Reloading...")
255 self:Reload()
256 end
257
258
259end
260
261function SWEP:DryFire(setnext)
262 if CLIENT and LocalPlayer() == self:GetOwner() then
263 self:EmitSound( "Weapon_Pistol.Empty" )
264 end
265
266 setnext(self, CurTime() + 0.2)
267
268 self:Reload()
269end
270
271function SWEP:CanPrimaryAttack()
272 if not IsValid(self:GetOwner()) then return end
273
274 if self:Clip1() <= 0 then
275 self:DryFire(self.SetNextPrimaryFire)
276 return false
277 end
278 return true
279end
280
281function SWEP:CanSecondaryAttack()
282 if not IsValid(self:GetOwner()) then return end
283
284 if self:Clip2() <= 0 then
285 self:DryFire(self.SetNextSecondaryFire)
286 return false
287 end
288 return true
289end
290
291local function Sparklies(attacker, tr, dmginfo)
292 if tr.HitWorld and tr.MatType == MAT_METAL then
293 local eff = EffectData()
294 eff:SetOrigin(tr.HitPos)
295 eff:SetNormal(tr.HitNormal)
296 util.Effect("cball_bounce", eff)
297 end
298end
299
300function SWEP:ShootBullet( dmg, recoil, numbul, cone )
301
302 self:SendWeaponAnim(self.PrimaryAnim)
303
304 self:GetOwner():MuzzleFlash()
305 self:GetOwner():SetAnimation( PLAYER_ATTACK1 )
306
307 local sights = self:GetIronsights()
308
309 numbul = numbul or 1
310 cone = cone or 0.01
311
312 local bullet = {}
313 bullet.Num = numbul
314 bullet.Src = self:GetOwner():GetShootPos()
315 bullet.Dir = self:GetOwner():GetAimVector()
316 bullet.Spread = Vector( cone, cone, 0 )
317 bullet.Tracer = 4
318 bullet.TracerName = self.Tracer or "Tracer"
319 bullet.Force = 10
320 bullet.Damage = dmg
321 if CLIENT and sparkle:GetBool() then
322 bullet.Callback = Sparklies
323 end
324
325 self:GetOwner():FireBullets( bullet )
326
327 -- Owner can die after firebullets
328 if (not IsValid(self:GetOwner())) or (not self:GetOwner():Alive()) or self:GetOwner():IsNPC() then return end
329
330 if ((game.SinglePlayer() and SERVER) or
331 ((not game.SinglePlayer()) and CLIENT and IsFirstTimePredicted())) then
332
333 -- reduce recoil if ironsighting
334 recoil = sights and (recoil * 0.6) or recoil
335
336 local eyeang = self:GetOwner():EyeAngles()
337 eyeang.pitch = eyeang.pitch - recoil
338 self:GetOwner():SetEyeAngles( eyeang )
339 end
340end
341
342function SWEP:GetPrimaryCone()
343 local cone = self.Primary.Cone or 0.2
344 -- 10% accuracy bonus when sighting
345 return self:GetIronsights() and (cone * 0.85) or cone
346end
347
348function SWEP:GetHeadshotMultiplier(victim, dmginfo)
349 return self.HeadshotMultiplier
350end
351
352function SWEP:IsEquipment()
353 return WEPS.IsEquipment(self)
354end
355
356function SWEP:DrawWeaponSelection() end
357
358function SWEP:SecondaryAttack()
359 if self.NoSights or (not self.IronSightsPos) then return end
360
361 self:SetIronsights(not self:GetIronsights())
362
363 self:SetNextSecondaryFire(CurTime() + 0.3)
364end
365
366function SWEP:Deploy()
367 self:SetIronsights(false)
368 return true
369end
370
371-- relevant stuff here
372function SWEP:Reload()
373
374 if ( self:Clip1() == self.Primary.ClipSize ) then return end -- if clip is not full
375 self:SetReload(true)
376 self:DefaultReload(self.ReloadAnim)
377 timer.Simple(20, function() self:SetReload(false) end)
378 self:SetIronsights( false )
379end
380
381function SWEP:SetReload(reloadstate)
382 self.Reloading = reloadstate
383end
384
385function SWEP:GetReload()
386 return self.Reloading
387end
388-- end relevant stuff
389
390function SWEP:OnRestore()
391 self.NextSecondaryAttack = 0
392 self:SetIronsights( false )
393end
394
395function SWEP:Ammo1()
396 return IsValid(self:GetOwner()) and self:GetOwner():GetAmmoCount(self.Primary.Ammo) or false
397end
398
399-- The OnDrop() hook is useless for this as it happens AFTER the drop. OwnerChange
400-- does not occur when a drop happens for some reason. Hence this thing.
401function SWEP:PreDrop()
402 if SERVER and IsValid(self:GetOwner()) and self.Primary.Ammo != "none" then
403 local ammo = self:Ammo1()
404
405 -- Do not drop ammo if we have another gun that uses this type
406 for _, w in pairs(self:GetOwner():GetWeapons()) do
407 if IsValid(w) and w != self and w:GetPrimaryAmmoType() == self:GetPrimaryAmmoType() then
408 ammo = 0
409 end
410 end
411
412 self.StoredAmmo = ammo
413
414 if ammo > 0 then
415 self:GetOwner():RemoveAmmo(ammo, self.Primary.Ammo)
416 end
417 end
418end
419
420function SWEP:DampenDrop()
421 -- For some reason gmod drops guns on death at a speed of 400 units, which
422 -- catapults them away from the body. Here we want people to actually be able
423 -- to find a given corpse's weapon, so we override the velocity here and call
424 -- this when dropping guns on death.
425 local phys = self:GetPhysicsObject()
426 if IsValid(phys) then
427 phys:SetVelocityInstantaneous(Vector(0,0,-75) + phys:GetVelocity() * 0.001)
428 phys:AddAngleVelocity(phys:GetAngleVelocity() * -0.99)
429 end
430end
431
432local SF_WEAPON_START_CONSTRAINED = 1
433
434-- Picked up by player. Transfer of stored ammo and such.
435function SWEP:Equip(newowner)
436 if SERVER then
437 if self:IsOnFire() then
438 self:Extinguish()
439 end
440
441 self.fingerprints = self.fingerprints or {}
442
443 if not table.HasValue(self.fingerprints, newowner) then
444 table.insert(self.fingerprints, newowner)
445 end
446
447 if self:HasSpawnFlags(SF_WEAPON_START_CONSTRAINED) then
448 -- If this weapon started constrained, unset that spawnflag, or the
449 -- weapon will be re-constrained and float
450 local flags = self:GetSpawnFlags()
451 local newflags = bit.band(flags, bit.bnot(SF_WEAPON_START_CONSTRAINED))
452 self:SetKeyValue("spawnflags", newflags)
453 end
454 end
455
456 if SERVER and IsValid(newowner) and self.StoredAmmo > 0 and self.Primary.Ammo != "none" then
457 local ammo = newowner:GetAmmoCount(self.Primary.Ammo)
458 local given = math.min(self.StoredAmmo, self.Primary.ClipMax - ammo)
459
460 newowner:GiveAmmo( given, self.Primary.Ammo)
461 self.StoredAmmo = 0
462 end
463end
464
465-- We were bought as special equipment, some weapons will want to do something
466-- extra for their buyer
467function SWEP:WasBought(buyer)
468end
469
470function SWEP:SetIronsights(b)
471 if (b ~= self:GetIronsights()) then
472 self:SetIronsightsPredicted(b)
473 self:SetIronsightsTime(CurTime())
474 if CLIENT then
475 self:CalcViewModel()
476 end
477 end
478end
479function SWEP:GetIronsights()
480 return self:GetIronsightsPredicted()
481end
482
483--- Dummy functions that will be replaced when SetupDataTables runs. These are
484--- here for when that does not happen (due to e.g. stacking base classes)
485function SWEP:GetIronsightsTime() return -1 end
486function SWEP:SetIronsightsTime() end
487function SWEP:GetIronsightsPredicted() return false end
488function SWEP:SetIronsightsPredicted() end
489
490-- Set up ironsights dt bool. Weapons using their own DT vars will have to make
491-- sure they call this.
492function SWEP:SetupDataTables()
493 self:NetworkVar("Bool", 3, "IronsightsPredicted")
494 self:NetworkVar("Float", 3, "IronsightsTime")
495end
496
497function SWEP:Initialize()
498 if CLIENT and self:Clip1() == -1 then
499 self:SetClip1(self.Primary.DefaultClip)
500 elseif SERVER then
501 self.fingerprints = {}
502
503 self:SetIronsights(false)
504 end
505
506 self:SetDeploySpeed(self.DeploySpeed)
507
508 -- compat for gmod update
509 if self.SetHoldType then
510 self:SetHoldType(self.HoldType or "pistol")
511 end
512end
513
514function SWEP:CalcViewModel()
515 if (not CLIENT) or (not IsFirstTimePredicted()) then return end
516 self.bIron = self:GetIronsights()
517 self.fIronTime = self:GetIronsightsTime()
518 self.fCurrentTime = CurTime()
519 self.fCurrentSysTime = SysTime()
520end
521
522-- Note that if you override Think in your SWEP, you should call
523-- BaseClass.Think(self) so as not to break ironsights
524function SWEP:Think()
525 self:CalcViewModel()
526end
527
528function SWEP:DyingShot()
529 local fired = false
530 if self:GetIronsights() then
531 self:SetIronsights(false)
532
533 if self:GetNextPrimaryFire() > CurTime() then
534 return fired
535 end
536
537 -- Owner should still be alive here
538 if IsValid(self:GetOwner()) then
539 local punch = self.Primary.Recoil or 5
540
541 -- Punch view to disorient aim before firing dying shot
542 local eyeang = self:GetOwner():EyeAngles()
543 eyeang.pitch = eyeang.pitch - math.Rand(-punch, punch)
544 eyeang.yaw = eyeang.yaw - math.Rand(-punch, punch)
545 self:GetOwner():SetEyeAngles( eyeang )
546
547 MsgN(self:GetOwner():Nick() .. " fired his DYING SHOT")
548
549 self:GetOwner().dying_wep = self
550
551 self:PrimaryAttack(true)
552
553 fired = true
554 end
555 end
556
557 return fired
558end
559
560local ttt_lowered = CreateConVar("ttt_ironsights_lowered", "1", FCVAR_ARCHIVE)
561local host_timescale = GetConVar("host_timescale")
562
563local LOWER_POS = Vector(0, 0, -2)
564
565local IRONSIGHT_TIME = 0.25
566function SWEP:GetViewModelPosition( pos, ang )
567 if (not self.IronSightsPos) or (self.bIron == nil) then return pos, ang end
568
569 local bIron = self.bIron
570 local time = self.fCurrentTime + (SysTime() - self.fCurrentSysTime) * game.GetTimeScale() * host_timescale:GetFloat()
571
572 if bIron then
573 self.SwayScale = 0.3
574 self.BobScale = 0.1
575 else
576 self.SwayScale = 1.0
577 self.BobScale = 1.0
578 end
579
580 local fIronTime = self.fIronTime
581 if (not bIron) and fIronTime < time - IRONSIGHT_TIME then
582 return pos, ang
583 end
584
585 local mul = 1.0
586
587 if fIronTime > time - IRONSIGHT_TIME then
588 mul = math.Clamp( (time - fIronTime) / IRONSIGHT_TIME, 0, 1 )
589
590 if not bIron then mul = 1 - mul end
591 end
592
593 local offset = self.IronSightsPos + (ttt_lowered:GetBool() and LOWER_POS or vector_origin)
594
595 if self.IronSightsAng then
596 ang = ang * 1
597 ang:RotateAroundAxis( ang:Right(), self.IronSightsAng.x * mul )
598 ang:RotateAroundAxis( ang:Up(), self.IronSightsAng.y * mul )
599 ang:RotateAroundAxis( ang:Forward(), self.IronSightsAng.z * mul )
600 end
601
602 pos = pos + offset.x * ang:Right() * mul
603 pos = pos + offset.y * ang:Forward() * mul
604 pos = pos + offset.z * ang:Up() * mul
605
606 return pos, ang
607end