· 7 years ago · Oct 24, 2018, 08:04 PM
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 = -1
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.fingerprints = {}
126
127local sparkle = CLIENT and CreateConVar("ttt_crazy_sparks", "0", FCVAR_ARCHIVE)
128
129-- crosshair
130if CLIENT then
131 local sights_opacity = CreateConVar("ttt_ironsights_crosshair_opacity", "0.8", FCVAR_ARCHIVE)
132 local crosshair_brightness = CreateConVar("ttt_crosshair_brightness", "1.0", FCVAR_ARCHIVE)
133 local crosshair_size = CreateConVar("ttt_crosshair_size", "1.0", FCVAR_ARCHIVE)
134 local disable_crosshair = CreateConVar("ttt_disable_crosshair", "0", FCVAR_ARCHIVE)
135
136 function SWEP:DrawHUD()
137 if self.HUDHelp then
138 self:DrawHelp()
139 end
140
141 local client = LocalPlayer()
142 if disable_crosshair:GetBool() or (not IsValid(client)) then return end
143
144 local sights = (not self.NoSights) and self:GetIronsights()
145
146 local x = math.floor(ScrW() / 2.0)
147 local y = math.floor(ScrH() / 2.0)
148 local scale = math.max(0.2, 10 * self:GetPrimaryCone())
149
150 local LastShootTime = self:LastShootTime()
151 scale = scale * (2 - math.Clamp( (CurTime() - LastShootTime) * 5, 0.0, 1.0 ))
152
153 local alpha = sights and sights_opacity:GetFloat() or 1
154 local bright = crosshair_brightness:GetFloat() or 1
155
156 -- somehow it seems this can be called before my player metatable
157 -- additions have loaded
158 if client.IsTraitor and client:IsTraitor() then
159 surface.SetDrawColor(255 * bright,
160 50 * bright,
161 50 * bright,
162 255 * alpha)
163 else
164 surface.SetDrawColor(0,
165 255 * bright,
166 0,
167 255 * alpha)
168 end
169
170 local gap = math.floor(20 * scale * (sights and 0.8 or 1))
171 local length = math.floor(gap + (25 * crosshair_size:GetFloat()) * scale)
172 surface.DrawLine( x - length, y, x - gap, y )
173 surface.DrawLine( x + length, y, x + gap, y )
174 surface.DrawLine( x, y - length, x, y - gap )
175 surface.DrawLine( x, y + length, x, y + gap )
176 end
177
178 local GetPTranslation = LANG.GetParamTranslation
179
180 -- Many non-gun weapons benefit from some help
181 local help_spec = {text = "", font = "TabLarge", xalign = TEXT_ALIGN_CENTER}
182 function SWEP:DrawHelp()
183 local data = self.HUDHelp
184
185 local translate = data.translatable
186 local primary = data.primary
187 local secondary = data.secondary
188
189 if translate then
190 primary = primary and GetPTranslation(primary, data.translate_params)
191 secondary = secondary and GetPTranslation(secondary, data.translate_params)
192 end
193
194 help_spec.pos = {ScrW() / 2.0, ScrH() - 40}
195 help_spec.text = secondary or primary
196 draw.TextShadow(help_spec, 2)
197
198 -- if no secondary exists, primary is drawn at the bottom and no top line
199 -- is drawn
200 if secondary then
201 help_spec.pos[2] = ScrH() - 60
202 help_spec.text = primary
203 draw.TextShadow(help_spec, 2)
204 end
205 end
206
207 -- mousebuttons are enough for most weapons
208 local default_key_params = {
209 primaryfire = Key("+attack", "LEFT MOUSE"),
210 secondaryfire = Key("+attack2", "RIGHT MOUSE"),
211 usekey = Key("+use", "USE")
212 };
213
214 function SWEP:AddHUDHelp(primary_text, secondary_text, translate, extra_params)
215 extra_params = extra_params or {}
216
217 self.HUDHelp = {
218 primary = primary_text,
219 secondary = secondary_text,
220 translatable = translate,
221 translate_params = table.Merge(extra_params, default_key_params)
222 };
223 end
224end
225
226-- Shooting functions largely copied from weapon_cs_base
227function SWEP:PrimaryAttack(worldsnd)
228
229 self:SetNextSecondaryFire( CurTime() + self.Primary.Delay )
230 self:SetNextPrimaryFire( CurTime() + self.Primary.Delay )
231
232 if not self:CanPrimaryAttack() then return end
233
234 if not worldsnd then
235 self:EmitSound( self.Primary.Sound, self.Primary.SoundLevel )
236 elseif SERVER then
237 sound.Play(self.Primary.Sound, self:GetPos(), self.Primary.SoundLevel)
238 end
239
240 self:ShootBullet( self.Primary.Damage, self.Primary.Recoil, self.Primary.NumShots, self:GetPrimaryCone() )
241
242 self:TakePrimaryAmmo( 1 )
243
244 local owner = self:GetOwner()
245 if not IsValid(owner) or owner:IsNPC() or (not owner.ViewPunch) then return end
246
247 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 ) )
248end
249
250function SWEP:DryFire(setnext)
251 if CLIENT and LocalPlayer() == self:GetOwner() then
252 self:EmitSound( "Weapon_Pistol.Empty" )
253 end
254
255 setnext(self, CurTime() + 0.2)
256
257 self:Reload()
258end
259
260function SWEP:CanPrimaryAttack()
261 if not IsValid(self:GetOwner()) then return end
262
263 if self:Clip1() <= 0 then
264 self:DryFire(self.SetNextPrimaryFire)
265 return false
266 end
267 return true
268end
269
270function SWEP:CanSecondaryAttack()
271 if not IsValid(self:GetOwner()) then return end
272
273 if self:Clip2() <= 0 then
274 self:DryFire(self.SetNextSecondaryFire)
275 return false
276 end
277 return true
278end
279
280local function Sparklies(attacker, tr, dmginfo)
281 if tr.HitWorld and tr.MatType == MAT_METAL then
282 local eff = EffectData()
283 eff:SetOrigin(tr.HitPos)
284 eff:SetNormal(tr.HitNormal)
285 util.Effect("cball_bounce", eff)
286 end
287end
288
289function SWEP:ShootBullet( dmg, recoil, numbul, cone )
290
291 self:SendWeaponAnim(self.PrimaryAnim)
292
293 self:GetOwner():MuzzleFlash()
294 self:GetOwner():SetAnimation( PLAYER_ATTACK1 )
295
296 local sights = self:GetIronsights()
297
298 numbul = numbul or 1
299 cone = cone or 0.01
300
301 local bullet = {}
302 bullet.Num = numbul
303 bullet.Src = self:GetOwner():GetShootPos()
304 bullet.Dir = self:GetOwner():GetAimVector()
305 bullet.Spread = Vector( cone, cone, 0 )
306 bullet.Tracer = 4
307 bullet.TracerName = self.Tracer or "Tracer"
308 bullet.Force = 10
309 bullet.Damage = dmg
310 if CLIENT and sparkle:GetBool() then
311 bullet.Callback = Sparklies
312 end
313
314 self:GetOwner():FireBullets( bullet )
315
316 -- Owner can die after firebullets
317 if (not IsValid(self:GetOwner())) or (not self:GetOwner():Alive()) or self:GetOwner():IsNPC() then return end
318
319 if ((game.SinglePlayer() and SERVER) or
320 ((not game.SinglePlayer()) and CLIENT and IsFirstTimePredicted())) then
321
322 -- reduce recoil if ironsighting
323 recoil = sights and (recoil * 0.6) or recoil
324
325 local eyeang = self:GetOwner():EyeAngles()
326 eyeang.pitch = eyeang.pitch - recoil
327 self:GetOwner():SetEyeAngles( eyeang )
328 end
329end
330
331function SWEP:GetPrimaryCone()
332 local cone = self.Primary.Cone or 0.2
333 -- 10% accuracy bonus when sighting
334 return self:GetIronsights() and (cone * 0.85) or cone
335end
336
337function SWEP:GetHeadshotMultiplier(victim, dmginfo)
338 return self.HeadshotMultiplier
339end
340
341function SWEP:IsEquipment()
342 return WEPS.IsEquipment(self)
343end
344
345function SWEP:DrawWeaponSelection() end
346
347function SWEP:SecondaryAttack()
348 if self.NoSights or (not self.IronSightsPos) then return end
349
350 self:SetIronsights(not self:GetIronsights())
351
352 self:SetNextSecondaryFire(CurTime() + 0.3)
353end
354
355function SWEP:Deploy()
356 self:SetIronsights(false)
357 return true
358end
359
360function SWEP:Reload()
361 if ( self:Clip1() == self.Primary.ClipSize or self:GetOwner():GetAmmoCount( self.Primary.Ammo ) <= 0 ) then return end
362 self:DefaultReload(self.ReloadAnim)
363 self:SetIronsights( false )
364end
365
366
367function SWEP:OnRestore()
368 self.NextSecondaryAttack = 0
369 self:SetIronsights( false )
370end
371
372function SWEP:Ammo1()
373 return IsValid(self:GetOwner()) and self:GetOwner():GetAmmoCount(self.Primary.Ammo) or false
374end
375
376-- The OnDrop() hook is useless for this as it happens AFTER the drop. OwnerChange
377-- does not occur when a drop happens for some reason. Hence this thing.
378function SWEP:PreDrop()
379 if SERVER and IsValid(self:GetOwner()) and self.Primary.Ammo != "none" then
380 local ammo = self:Ammo1()
381
382 -- Do not drop ammo if we have another gun that uses this type
383 for _, w in pairs(self:GetOwner():GetWeapons()) do
384 if IsValid(w) and w != self and w:GetPrimaryAmmoType() == self:GetPrimaryAmmoType() then
385 ammo = 0
386 end
387 end
388
389 self.StoredAmmo = ammo
390
391 if ammo > 0 then
392 self:GetOwner():RemoveAmmo(ammo, self.Primary.Ammo)
393 end
394 end
395end
396
397function SWEP:DampenDrop()
398 -- For some reason gmod drops guns on death at a speed of 400 units, which
399 -- catapults them away from the body. Here we want people to actually be able
400 -- to find a given corpse's weapon, so we override the velocity here and call
401 -- this when dropping guns on death.
402 local phys = self:GetPhysicsObject()
403 if IsValid(phys) then
404 phys:SetVelocityInstantaneous(Vector(0,0,-75) + phys:GetVelocity() * 0.001)
405 phys:AddAngleVelocity(phys:GetAngleVelocity() * -0.99)
406 end
407end
408
409local SF_WEAPON_START_CONSTRAINED = 1
410
411-- Picked up by player. Transfer of stored ammo and such.
412function SWEP:Equip(newowner)
413 if SERVER then
414 if self:IsOnFire() then
415 self:Extinguish()
416 end
417
418 self.fingerprints = self.fingerprints or {}
419
420 if not table.HasValue(self.fingerprints, newowner) then
421 table.insert(self.fingerprints, newowner)
422 end
423
424 if self:HasSpawnFlags(SF_WEAPON_START_CONSTRAINED) then
425 -- If this weapon started constrained, unset that spawnflag, or the
426 -- weapon will be re-constrained and float
427 local flags = self:GetSpawnFlags()
428 local newflags = bit.band(flags, bit.bnot(SF_WEAPON_START_CONSTRAINED))
429 self:SetKeyValue("spawnflags", newflags)
430 end
431 end
432
433 if SERVER and IsValid(newowner) and self.StoredAmmo > 0 and self.Primary.Ammo != "none" then
434 local ammo = newowner:GetAmmoCount(self.Primary.Ammo)
435 local given = math.min(self.StoredAmmo, self.Primary.ClipMax - ammo)
436
437 newowner:GiveAmmo( given, self.Primary.Ammo)
438 self.StoredAmmo = 0
439 end
440end
441
442-- We were bought as special equipment, some weapons will want to do something
443-- extra for their buyer
444function SWEP:WasBought(buyer)
445end
446
447function SWEP:SetIronsights(b)
448 if (b ~= self:GetIronsights()) then
449 self:SetIronsightsPredicted(b)
450 self:SetIronsightsTime(CurTime())
451 if CLIENT then
452 self:CalcViewModel()
453 end
454 end
455end
456function SWEP:GetIronsights()
457 return self:GetIronsightsPredicted()
458end
459
460--- Dummy functions that will be replaced when SetupDataTables runs. These are
461--- here for when that does not happen (due to e.g. stacking base classes)
462function SWEP:GetIronsightsTime() return -1 end
463function SWEP:SetIronsightsTime() end
464function SWEP:GetIronsightsPredicted() return false end
465function SWEP:SetIronsightsPredicted() end
466
467-- Set up ironsights dt bool. Weapons using their own DT vars will have to make
468-- sure they call this.
469function SWEP:SetupDataTables()
470 self:NetworkVar("Bool", 3, "IronsightsPredicted")
471 self:NetworkVar("Float", 3, "IronsightsTime")
472end
473
474function SWEP:Initialize()
475 if CLIENT and self:Clip1() == -1 then
476 self:SetClip1(self.Primary.DefaultClip)
477 elseif SERVER then
478 self.fingerprints = {}
479
480 self:SetIronsights(false)
481 end
482
483 self:SetDeploySpeed(self.DeploySpeed)
484
485 -- compat for gmod update
486 if self.SetHoldType then
487 self:SetHoldType(self.HoldType or "pistol")
488 end
489end
490
491function SWEP:CalcViewModel()
492 if (not CLIENT) or (not IsFirstTimePredicted()) then return end
493 self.bIron = self:GetIronsights()
494 self.fIronTime = self:GetIronsightsTime()
495 self.fCurrentTime = CurTime()
496 self.fCurrentSysTime = SysTime()
497end
498
499-- Note that if you override Think in your SWEP, you should call
500-- BaseClass.Think(self) so as not to break ironsights
501function SWEP:Think()
502 self:CalcViewModel()
503end
504
505function SWEP:DyingShot()
506 local fired = false
507 if self:GetIronsights() then
508 self:SetIronsights(false)
509
510 if self:GetNextPrimaryFire() > CurTime() then
511 return fired
512 end
513
514 -- Owner should still be alive here
515 if IsValid(self:GetOwner()) then
516 local punch = self.Primary.Recoil or 5
517
518 -- Punch view to disorient aim before firing dying shot
519 local eyeang = self:GetOwner():EyeAngles()
520 eyeang.pitch = eyeang.pitch - math.Rand(-punch, punch)
521 eyeang.yaw = eyeang.yaw - math.Rand(-punch, punch)
522 self:GetOwner():SetEyeAngles( eyeang )
523
524 MsgN(self:GetOwner():Nick() .. " fired his DYING SHOT")
525
526 self:GetOwner().dying_wep = self
527
528 self:PrimaryAttack(true)
529
530 fired = true
531 end
532 end
533
534 return fired
535end
536
537local ttt_lowered = CreateConVar("ttt_ironsights_lowered", "1", FCVAR_ARCHIVE)
538local host_timescale = GetConVar("host_timescale")
539
540local LOWER_POS = Vector(0, 0, -2)
541
542local IRONSIGHT_TIME = 0.25
543function SWEP:GetViewModelPosition( pos, ang )
544 if (not self.IronSightsPos) or (self.bIron == nil) then return pos, ang end
545
546 local bIron = self.bIron
547 local time = self.fCurrentTime + (SysTime() - self.fCurrentSysTime) * game.GetTimeScale() * host_timescale:GetFloat()
548
549 if bIron then
550 self.SwayScale = 0.3
551 self.BobScale = 0.1
552 else
553 self.SwayScale = 1.0
554 self.BobScale = 1.0
555 end
556
557 local fIronTime = self.fIronTime
558 if (not bIron) and fIronTime < time - IRONSIGHT_TIME then
559 return pos, ang
560 end
561
562 local mul = 1.0
563
564 if fIronTime > time - IRONSIGHT_TIME then
565 mul = math.Clamp( (time - fIronTime) / IRONSIGHT_TIME, 0, 1 )
566
567 if not bIron then mul = 1 - mul end
568 end
569
570 local offset = self.IronSightsPos + (ttt_lowered:GetBool() and LOWER_POS or vector_origin)
571
572 if self.IronSightsAng then
573 ang = ang * 1
574 ang:RotateAroundAxis( ang:Right(), self.IronSightsAng.x * mul )
575 ang:RotateAroundAxis( ang:Up(), self.IronSightsAng.y * mul )
576 ang:RotateAroundAxis( ang:Forward(), self.IronSightsAng.z * mul )
577 end
578
579 pos = pos + offset.x * ang:Right() * mul
580 pos = pos + offset.y * ang:Forward() * mul
581 pos = pos + offset.z * ang:Up() * mul
582
583 return pos, ang
584end