-
Notifications
You must be signed in to change notification settings - Fork 88
Expand file tree
/
Copy pathsv_player.lua
More file actions
1463 lines (1216 loc) · 44.6 KB
/
Copy pathsv_player.lua
File metadata and controls
1463 lines (1216 loc) · 44.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
---
-- Player spawning/dying
-- @todo rework
-- @section player_manager
local math = math
local player = player
local net = net
local IsValid = IsValid
local hook = hook
---
-- @realm server
local ttt_bots_are_spectators = CreateConVar("ttt_bots_are_spectators", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE})
---
-- @realm server
local ttt_dyingshot = CreateConVar("ttt_dyingshot", "0", {FCVAR_NOTIFY, FCVAR_ARCHIVE})
---
-- @realm server
CreateConVar("ttt_killer_dna_range", "550", {FCVAR_NOTIFY, FCVAR_ARCHIVE})
---
-- @realm server
CreateConVar("ttt_killer_dna_basetime", "100", {FCVAR_NOTIFY, FCVAR_ARCHIVE})
util.AddNetworkString("ttt2_damage_received")
---
-- First spawn on the server.
-- Called when the @{Player} spawns for the first time.
-- See @{GM:PlayerSpawn} for a hook called every @{Player} spawn.
-- @note This hook is called before the @{Player} has fully loaded, when the @{Player} is still in seeing the "Starting Lua" screen.
-- For example, trying to use the @{Entity:GetModel} function will return the default model ("player/default.mdl")
-- @param Player ply The @{Player} who spawned.
-- @hook
-- @realm server
-- @ref https://wiki.facepunch.com/gmod/GM:PlayerInitialSpawn
-- @local
function GM:PlayerInitialSpawn(ply)
ply:InitialSpawn()
local rstate = GetRoundState() or ROUND_WAIT
-- We should update the traitor list, if we are not about to send it
-- sending roles for spectators
if rstate <= ROUND_PREP then
SendFullStateUpdate() -- TODO needed?
end
-- Game has started, tell this guy (spec) where the round is at
if rstate ~= ROUND_WAIT then
SendRoundState(rstate, ply)
timer.Simple(1, SendFullStateUpdate)
end
-- Handle spec bots
if ply:IsBot() and ttt_bots_are_spectators:GetBool() then
ply:SetTeam(TEAM_SPEC)
ply:SetForceSpec(true)
end
-- Sync NWVars
-- Needs to be done here, to include bots (also this wont send any net messages to the initialized player)
ttt2net.SyncWithNWVar("body_found", { type = "bool" }, ply, "body_found")
-- maybe show credits
net.Start("TTT2DevChanges")
net.Send(ply)
end
---
-- Called when a @{Player} has been validated by Steam.
-- @param string name Player name
-- @param string steamid Player SteamID
-- @hook
-- @realm server
-- @ref https://wiki.facepunch.com/gmod/GM:NetworkIDValidated
-- @local
function GM:NetworkIDValidated(name, steamid)
-- edge case where player authed after initspawn
local plys = player.GetAll()
for i = 1, #plys do
local p = plys[i]
if not IsValid(p) or p:SteamID64() ~= steamid or not p.delay_karma_recall then continue end
KARMA.LateRecallAndSet(p)
return
end
end
---
-- Called whenever a @{Player} spawns, including respawns.
-- @param Player ply The @{Player} who spawned
-- @hook
-- @realm server
-- @ref https://wiki.facepunch.com/gmod/GM:PlayerSpawn
-- @local
function GM:PlayerSpawn(ply)
-- reset any cached weapons
ply:ResetCachedWeapons()
-- stop bleeding
util.StopBleeding(ply)
-- Some spawns may be tilted
ply:ResetViewRoll()
-- Clear out stuff like whether we ordered guns or what bomb code we used
ply:ResetRoundFlags()
-- latejoiner, send him some info
if GetRoundState() == ROUND_ACTIVE then
SendRoundState(GetRoundState(), ply)
end
ply.spawn_nick = ply:Nick()
ply.has_spawned = true
-- let the client do things on spawn
net.Start("TTT_PlayerSpawned")
net.WriteBit(ply:IsSpec())
net.Send(ply)
if ply:IsSpec() then
ply:StripAll()
ply:Spectate(OBS_MODE_ROAMING)
return
end
ply:UnSpectate()
-- ye olde hooks
---
-- @realm server
hook.Run("PlayerLoadout", ply, false)
---
-- @realm server
hook.Run("PlayerSetModel", ply)
---
-- @realm server
hook.Run("TTTPlayerSetColor", ply)
ply:SetupHands()
ply:SetLastSpawnPosition(ply:GetPos())
ply:SetLastDeathPosition(nil)
if ply:IsActive() then
-- a function to handle the rolespecific stuff that should be done on
-- rolechange and respawn (while a round is active)
ply:GetSubRoleData():GiveRoleLoadout(ply, false)
events.Trigger(EVENT_RESPAWN, ply)
else
events.Trigger(EVENT_SPAWN, ply)
end
end
---
-- Called whenever view model hands needs setting a model.
-- By default this calls @{Player:GetHandsModel} and if that fails,
-- sets the hands model according to his @{Player} model.
-- @param Player ply The @{Player} whose hands needs a model set
-- @param Entity ent The hands to set model of
-- @hook
-- @realm server
-- @ref https://wiki.facepunch.com/gmod/GM:PlayerSetHandsModel
-- @local
function GM:PlayerSetHandsModel(ply, ent)
local simplemodel = player_manager.TranslateToPlayerModelName(ply:GetModel())
local info = player_manager.TranslatePlayerHands(simplemodel)
if info then
ent:SetModel(info.model)
ent:SetSkin(info.skin)
ent:SetBodyGroups(info.body)
end
end
---
-- Check if a @{Player} can spawn at a certain spawnpoint.
-- @param Player ply The @{Player} who is spawned
-- @param Entity spawnEntity The spawnpoint entity (on the map)
-- @param boolean force If this is true, it'll kill any players blocking the spawnpoint
-- @return boolean Return true to indicate that the spawnpoint is suitable (Allow for the @{Player} to spawn here), false to prevent spawning
-- @hook
-- @realm server
-- @ref https://wiki.facepunch.com/gmod/GM:IsSpawnpointSuitable
-- @local
function GM:IsSpawnpointSuitable(ply, spawnEntity, force)
if not IsValid(ply) or not ply:IsTerror() then
return true
end
return plyspawn.IsSpawnPointSafe(ply, spawnEntity:GetPos(), force)
end
---
-- Returns a list of all spawnable @{Entity}
-- @param boolean shuffle whether the table should be shuffled
-- @param boolean forceAll used unless absolutely necessary (includes info_player_start spawns)
-- @return table
-- @deprecated Use @{plyspawn.GetPlayerSpawnPoints} instead
-- @realm server
function GetSpawnEnts(shouldShuffle, forceAll)
-- forceAll is ignored because the new system doesn't use it anymore
local spawnEntities = plyspawn.GetPlayerSpawnPoints()
if shouldShuffle then
table.Shuffle(spawnEntities)
end
return spawnEntities
end
---
-- Called to determine a spawn point for a @{Player} to spawn at.
-- @note This hook is not used to determine the spawn point of the player.
-- @param Player ply The @{Player} who needs a spawn point
-- @param boolean transition If true, the player just spawned from a map transition (trigger_changelevel);
-- you probably want to not return an entity for that case to not override player's position
-- @return Entity The spawnpoint entity to spawn the @{Player} at
-- @hook
-- @realm server
-- @ref https://wiki.facepunch.com/gmod/GM:PlayerSelectSpawn
-- @local
function GM:PlayerSelectSpawn(ply, transition)
-- this overwrite is needed to suppress the GMod warning if no spawn entities were found
-- "[PlayerSelectSpawn] Error! No spawn points!"
end
---
-- Called whenever a @{Player} spawns and must choose a model.
-- A good place to assign a model to a @{Player}.
-- @note This function may not work in your custom gamemode if you have overridden
-- your @{GM:PlayerSpawn} and you do not use self.BaseClass.PlayerSpawn or @{hook.Run}.
-- @param Player ply The @{Player} being chosen
-- @hook
-- @realm server
-- @ref https://wiki.facepunch.com/gmod/GM:PlayerSetModel
-- @local
function GM:PlayerSetModel(ply)
-- The player modes has to be applied here since some player model selectors overwrite
-- this hook to suppress the TTT2 player models. If the model is assigned elsewhere, it
-- breaks with external model selectors.
if not IsValid(ply) then return end
-- this will call the overwritten internal function to modify the model
ply:SetModel(ply.defaultModel or GAMEMODE.playermodel)
-- Always clear color state, may later be changed in TTTPlayerSetColor
ply:SetColor(COLOR_WHITE)
end
---
-- Called when a @{Player} spawns and updates the @{Color}
-- @param Player ply
-- @hook
-- @realm server
function GM:TTTPlayerSetColor(ply)
if not GetConVar("ttt_enforce_playercolor"):GetBool() then return end
local c = COLOR_WHITE
if GAMEMODE.playercolor then
-- If this player has a colorable model, always use the same color as all
-- other colorable players, so color will never be the factor that lets
-- you tell players apart.
c = GAMEMODE.playercolor
end
ply:SetPlayerColor(Vector(c.r / 255.0, c.g / 255.0, c.b / 255.0))
end
---
-- Determines if the @{Player} can kill themselves using the concommands "kill" or "explode".
-- Only active players can use kill cmd
-- @param Player ply
-- @return boolean True if they can suicide.
-- @hook
-- @realm server
-- @ref https://wiki.facepunch.com/gmod/GM:CanPlayerSuicide
-- @local
function GM:CanPlayerSuicide(ply)
return ply:IsTerror()
end
---
-- Called whenever a @{Player} attempts to either turn on or off their flashlight,
-- returning false will deny the change.
-- @note Also gets called when using @{Player:Flashlight}
-- @param Player ply The @{Player} who attempts to change their flashlight state.
-- @param boolean on The new state the @{Player} requested, true for on, false for off.
-- @return boolean Can toggle the flashlight or not
-- @hook
-- @realm server
-- @ref https://wiki.facepunch.com/gmod/GM:PlayerSwitchFlashlight
-- @local
function GM:PlayerSwitchFlashlight(ply, on)
if not IsValid(ply) then
return false
end
-- add the flashlight "effect" here, and then deny the switch
-- this prevents the sound from playing, fixing the exploit
-- where weapon sound could be silenced using the flashlight sound
if on and ply:IsTerror() then
ply:AddEffects(EF_DIMLIGHT)
else
ply:RemoveEffects(EF_DIMLIGHT)
end
return false
end
---
-- Determines if the @{Player} can spray using the "impulse 201" console command.
-- @note This is blocked if a @{Player} isn't a terrorist
-- @param Player ply
-- @return boolean Return false to allow spraying, return true to prevent spraying.
-- @hook
-- @realm server
-- @ref https://wiki.facepunch.com/gmod/GM:PlayerSpray
-- @local
function GM:PlayerSpray(ply)
if not IsValid(ply) or not ply:IsTerror() then
return true -- block
end
end
---
-- Triggered when the @{Player} presses use on an object.
-- Continuously runs until USE is released but will not activate other Entities
-- until the USE key is released; dependent on activation type of the @{Entity}.
-- @note This is just allowed for terrorists
-- @param Player ply The @{Player} pressing the "use" key.
-- @param Entity ent The entity which the @{Player} is looking at / activating USE on.
-- @return boolean Return false if the @{Player} is not allowed to USE the entity.
-- Do not return true if using a hook, otherwise other mods may not get a chance to block a @{Player}'s use.
-- @hook
-- @realm server
-- @ref https://wiki.facepunch.com/gmod/GM:PlayerUse
-- @local
function GM:PlayerUse(ply, ent)
return ply:IsTerror()
end
---
-- Called whenever a @{Player} pressed a key included within the IN keys.
-- For a more general purpose function that handles all kinds of input, see @{GM:PlayerButtonDown}
-- @warning Due to this being a predicted hook, @{ParticleEffects} created only serverside
-- from this hook will not be networked to the client, so make sure to do that on both realms
-- @predicted
-- @param Player ply The @{Player} pressing the key. If running client-side, this will always be @{LocalPlayer}
-- @param number key The key that the @{Player} pressed using <a href="https://wiki.garrysmod.com/page/Enums/IN">IN_Enums</a>.
-- @hook
-- @realm server
-- @ref https://wiki.facepunch.com/gmod/GM:KeyPress
-- @local
function GM:KeyPress(ply, key)
if not IsValid(ply) then return end
-- Spectator keys
if not ply:IsSpec() or ply:GetRagdollSpec() then return end
-- Do not allow the spectator to gather information if they're about to revive.
if ply:IsReviving() then
LANG.Msg(ply, "spec_about_to_revive", nil, MSG_MSTACK_WARN)
return
end
if ply.propspec then
return PROPSPEC.Key(ply, key)
end
ply:ResetViewRoll()
if key == IN_ATTACK then
-- snap to random guy
ply:Spectate(OBS_MODE_ROAMING)
ply:SetEyeAngles(angle_zero) -- After exiting propspec, this could be set to awkward values
ply:SpectateEntity(nil)
local alive = util.GetAlivePlayers()
local alive_count = #alive
if alive_count < 1 then return end
local target = alive[math.random(alive_count)]
if IsValid(target) then
--ply:SetPos(target:EyePos())
--ply:SetEyeAngles(target:EyeAngles())
ply:Spectate(OBS_MODE_IN_EYE)
ply:SpectateEntity(target)
end
elseif key == IN_ATTACK2 then
-- spectate either the next guy or a random guy in chase
local target = util.GetNextAlivePlayer(ply:GetObserverTarget())
if IsValid(target) then
ply:Spectate(ply.spec_mode or OBS_MODE_IN_EYE)
ply:SpectateEntity(target)
end
elseif key == IN_DUCK then
local pos = ply:GetPos()
local ang = ply:EyeAngles()
local target = ply:GetObserverTarget()
-- Only set the spectator's position to the player they are spectating if they are in chase or eye mode.
-- They can use the reload key if they want to return to the person they're spectating
if IsValid(target) and target:IsPlayer() and ply:GetObserverMode() ~= OBS_MODE_ROAMING then
pos = target:EyePos()
ang = target:EyeAngles()
end
-- reset
ply:Spectate(OBS_MODE_ROAMING)
ply:SpectateEntity(nil)
ply:SetPos(pos)
ply:SetEyeAngles(ang)
return true
elseif key == IN_JUMP then
-- unfuck if you're on a ladder etc
if ply:GetMoveType() ~= MOVETYPE_NOCLIP then
ply:SetMoveType(MOVETYPE_NOCLIP)
end
elseif key == IN_RELOAD then
local tgt = ply:GetObserverTarget()
if not IsValid(tgt) or not tgt:IsPlayer() then return end
if not ply.spec_mode or ply.spec_mode == OBS_MODE_IN_EYE then
ply.spec_mode = OBS_MODE_CHASE
elseif ply.spec_mode == OBS_MODE_CHASE then
ply.spec_mode = OBS_MODE_IN_EYE
end
-- roam stays roam
ply:Spectate(ply.spec_mode)
end
end
---
-- Runs when a IN key was released by a player.
-- For a more general purpose @{function} that handles all kinds of input, see @{GM:PlayerButtonUp}
-- @predicted
-- @param Player ply The @{Player} pressing the key. If running client-side, this will always be @{LocalPlayer}
-- @param number key The key that the @{Player} pressed using <a href="https://wiki.garrysmod.com/page/Enums/IN">IN_Enums</a>.
-- @hook
-- @realm server
-- @ref https://wiki.facepunch.com/gmod/GM:KeyRelease
-- @local
function GM:KeyRelease(ply, key)
if key ~= IN_USE or not IsValid(ply) or not ply:IsTerror() then return end
-- see if we need to do some custom usekey overriding
local tr = util.TraceLine({
start = ply:GetShootPos(),
endpos = ply:GetShootPos() + ply:GetAimVector() * 100,
filter = ply,
mask = MASK_SHOT
})
if not tr.Hit or not IsValid(tr.Entity) then return end
if tr.Entity.CanUseKey and tr.Entity.UseOverride then
local phys = tr.Entity:GetPhysicsObject()
if IsValid(phys) and not phys:HasGameFlag(FVPHYSICS_PLAYER_HELD) then
tr.Entity:UseOverride(ply)
return true
else
-- do nothing, can't +use held objects
return true
end
elseif tr.Entity.player_ragdoll then
CORPSE.ShowSearch(ply, tr.Entity, ply:KeyDown(IN_WALK) or ply:KeyDownLast(IN_WALK)) -- Body Corpse Search Identify TODO
return true
end
end
---
-- Normally all dead players are blocked from IN_USE on the server, meaning we
-- can't let them search bodies. This sucks because searching bodies is
-- fun. Hence on the client we override +use for specs and use this instead.
local function SpecUseKey(ply, cmd, arg)
if not IsValid(ply) or not ply:IsSpec() then return end
-- Do not allow the spectator to gather information if they're about to revive.
if ply:IsReviving() then
LANG.Msg(ply, "spec_about_to_revive", nil, MSG_MSTACK_WARN)
return
end
-- longer range than normal use
local tr = util.QuickTrace(ply:GetShootPos(), ply:GetAimVector() * 128, ply)
if not tr.Hit or not IsValid(tr.Entity) then return end
if tr.Entity.player_ragdoll then
if not ply:KeyDown(IN_WALK) then
CORPSE.ShowSearch(ply, tr.Entity)
else
ply:Spectate(OBS_MODE_IN_EYE)
ply:SpectateEntity(tr.Entity)
end
elseif tr.Entity:IsPlayer() and tr.Entity:IsActive() then
ply:Spectate(ply.spec_mode or OBS_MODE_IN_EYE)
ply:SpectateEntity(tr.Entity)
else
PROPSPEC.Target(ply, tr.Entity)
end
end
concommand.Add("ttt_spec_use", SpecUseKey)
---
-- Called when a @{Player} leaves the server. See the <a href="https://wiki.garrysmod.com/page/Game_Events">player_disconnect gameevent</a> for a shared version of this hook.
-- @param Player ply
-- @hook
-- @realm server
-- @ref https://wiki.facepunch.com/gmod/GM:PlayerDisconnected
-- @local
function GM:PlayerDisconnected(ply)
if IsValid(ply) then
-- Kill the player when necessary
if ply:IsTerror() and ply:Alive() then
ply:Kill()
end
-- TODO ?
-- Prevent the disconnected player from being in the resends
ply:SetRole(ROLE_NONE)
end
if GetRoundState() ~= ROUND_PREP then
TTT2NETTABLE[ply] = nil
SendFullStateUpdate()
end
if KARMA.IsEnabled() then
KARMA.Remember(ply)
end
ttt2net.ResetClient(ply)
end
---
-- Death affairs
local function CreateDeathEffect(ent, marked)
local pos = ent:GetPos() + Vector(0, 0, 20)
local jit = 35.0
local jitter = Vector(math.Rand(-jit, jit), math.Rand(-jit, jit), 0)
util.PaintDown(pos + jitter, "Blood", ent)
if marked then
util.PaintDown(pos, "Cross", ent)
end
end
local deathsounds = {
Sound("player/death1.wav"),
Sound("player/death2.wav"),
Sound("player/death3.wav"),
Sound("player/death4.wav"),
Sound("player/death5.wav"),
Sound("player/death6.wav"),
Sound("vo/npc/male01/pain07.wav"),
Sound("vo/npc/male01/pain08.wav"),
Sound("vo/npc/male01/pain09.wav"),
Sound("vo/npc/male01/pain04.wav"),
Sound("vo/npc/Barney/ba_pain06.wav"),
Sound("vo/npc/Barney/ba_pain07.wav"),
Sound("vo/npc/Barney/ba_pain09.wav"),
Sound("vo/npc/Barney/ba_ohshit03.wav"), --heh
Sound("vo/npc/Barney/ba_no01.wav"),
Sound("vo/npc/male01/no02.wav"),
Sound("hostage/hpain/hpain1.wav"),
Sound("hostage/hpain/hpain2.wav"),
Sound("hostage/hpain/hpain3.wav"),
Sound("hostage/hpain/hpain4.wav"),
Sound("hostage/hpain/hpain5.wav"),
Sound("hostage/hpain/hpain6.wav")
}
local deathsounds_count = #deathsounds
local function PlayDeathSound(victim)
if not IsValid(victim) then return end
sound.Play(deathsounds[math.random(deathsounds_count)], victim:GetShootPos(), 90, 100)
end
---
-- Handles the @{Player}'s death.<br />
-- This hook is not called if the @{Player} is killed by @{Player:KillSilent}.
-- See @{GM:PlayerSilentDeath} for that.
-- <ul>
-- <li>@{GM:PlayerDeath} is called after this hook</li>
-- <li>@{GM:PostPlayerDeath} is called after that</li>
-- </ul>
-- @note @{Player:Alive} returns true when this is called
-- @param Player ply
-- @param Player|Entity attacker @{Player} or @{Entity} that killed the @{Player}
-- @param DamageInfo dmginfo
-- @hook
-- @realm server
-- @ref https://wiki.facepunch.com/gmod/GM:DoPlayerDeath
-- @local
function GM:DoPlayerDeath(ply, attacker, dmginfo)
if ply:IsSpec() then return end
-- Experimental: Fire a last shot if ironsighting and not headshot
if ttt_dyingshot:GetBool() then
local wep = ply:GetActiveWeapon()
if IsValid(wep) and wep.DyingShot and not ply.was_headshot and dmginfo:IsBulletDamage() then
local fired = wep:DyingShot()
if fired then return end
end
-- Note that funny things can happen here because we fire a gun while the
-- player is dead. Specifically, this DoPlayerDeath is run twice for
-- him. This is ugly, and we have to return the first one to prevent crazy
-- shit.
end
-- Drop all weapons
local weps = ply:GetWeapons()
for i = 1, #weps do
local wep = weps[i]
WEPS.DropNotifiedWeapon(ply, wep, true) -- with ammo in them
if isfunction(wep.DampenDrop) then
wep:DampenDrop()
end
end
if IsValid(ply.hat) then
ply.hat:Drop()
end
-- Create ragdoll and hook up marking effects
local rag = CORPSE.Create(ply, attacker, dmginfo)
ply.server_ragdoll = rag -- nil if clientside
CreateDeathEffect(ply, false)
util.StartBleeding(rag, dmginfo:GetDamage(), 15)
-- Score only when there is a round active.
if GetRoundState() == ROUND_ACTIVE then
events.Trigger(EVENT_KILL, ply, attacker, dmginfo)
if IsValid(attacker) and attacker:IsPlayer() then
attacker:RecordKill(ply)
DamageLog(Format("KILL:\t %s [%s] killed %s [%s]", attacker:Nick(), attacker:GetRoleString(), ply:Nick(), ply:GetRoleString()))
else
DamageLog(Format("KILL:\t <something/world> killed %s [%s]", ply:Nick(), ply:GetRoleString()))
end
KARMA.Killed(attacker, ply, dmginfo)
end
-- Clear out any weapon or equipment we still have
ply:StripAll()
-- Tell the client to send their chat contents
ply:SendLastWords(dmginfo)
local killwep = util.WeaponFromDamage(dmginfo)
-- headshots, knife damage, and weapons tagged as silent all prevent death
-- sound from occurring
if not ply.was_headshot and not dmginfo:IsDamageType(DMG_SLASH) and not (IsValid(killwep) and killwep.IsSilent) then
PlayDeathSound(ply)
end
credits.HandleKillCreditsAward(ply, attacker)
end
---
-- Called when a @{Player} is killed by @{Player:Kill} or any other normal means.
-- This hook is not called if the @{Player} is killed by @{Player:KillSilent}. See @{GM:PlayerSilentDeath} for that.
-- <ul>
-- <li>@{GM:DoPlayerDeath} is called before this hook.</li>
-- <li>@{GM:PostPlayerDeath} is called after that</li>
-- </ul>
-- See @{Player:LastHitGroup} if you need to get the last hit hitgroup of the @{Player}.
-- @note @{Player:Alive} will return true in this hook
-- @param Player victim The @{Player} who died
-- @param Entity infl @{Entity} used to kill the victim
-- @param Player|Entity attacker @{Player} or @{Entity} that killed the victim
-- @hook
-- @realm server
-- @ref https://wiki.facepunch.com/gmod/GM:PlayerDeath
-- @local
function GM:PlayerDeath(victim, infl, attacker)
victim:SetLastDeathPosition(victim:GetPos())
victim:IncreaseRoundDeathCounter()
-- stop bleeding
util.StopBleeding(victim)
-- tell no one
self:PlayerSilentDeath(victim)
-- a function to handle the rolespecific stuff that should be done on
-- rolechange and respawn (while a round is active)
if victim:IsActive() then
victim:GetSubRoleData():RemoveRoleLoadout(victim, false)
end
victim:SetTeam(TEAM_SPEC)
victim:Freeze(false)
victim:SetRagdollSpec(true)
victim:Spectate(OBS_MODE_IN_EYE)
local rag_ent = victim.server_ragdoll or victim:GetRagdollEntity()
victim:SpectateEntity(rag_ent)
victim:Flashlight(false)
victim:Extinguish()
net.Start("TTT_PlayerDied")
net.Send(victim)
---
-- @realm server
if HasteMode() and GetRoundState() == ROUND_ACTIVE and not hook.Run("TTT2ShouldSkipHaste", victim, attacker) then
IncRoundEnd(GetConVar("ttt_haste_minutes_per_death"):GetFloat() * 60)
end
if IsValid(attacker) and attacker:IsPlayer() and attacker ~= victim and attacker:IsActive() then
victim.killerSpec = attacker
end
---
-- @realm server
hook.Run("TTT2PostPlayerDeath", victim, infl, attacker)
end
---
-- Called when the @{Player} is killed by @{Player:KillSilent}.
-- The player is already considered dead when this hook is called.
-- @param Player victim The player who was killed
-- @hook
-- @ref https://wiki.facepunch.com/gmod/GM:PlayerSilentDeath
-- @realm server
function GM:PlayerSilentDeath(victim)
victim:SetLastDeathPosition(victim:GetPos())
victim:IncreaseRoundDeathCounter()
end
---
-- Returns whether or not the default death sound should be muted.
-- @return[default=true] boolean Mute death sound
-- @note Used here to kill the hl2 beep
-- @hook
-- @realm server
-- @ref https://wiki.facepunch.com/gmod/GM:PlayerDeathSound
-- @local
function GM:PlayerDeathSound()
return true
end
---
-- Called right after @{GM:DoPlayerDeath}, @{GM:PlayerDeath} and @{GM:PlayerSilentDeath}.<br />
-- This hook will be called for all deaths, including @{Player:KillSilent}
-- @note The @{Player} is considered dead when this is hook is called, @{Player:Alive} will return false.
-- @param Player ply
-- @hook
-- @realm server
-- @ref https://wiki.facepunch.com/gmod/GM:PostPlayerDeath
-- @local
function GM:PostPlayerDeath(ply)
-- endless respawn player if he dies in preparing time
if GetRoundState() ~= ROUND_PREP or not GetConVar("ttt2_prep_respawn"):GetBool() then return end
timer.Simple(1, function()
if not IsValid(ply) then return end
ply:SpawnForRound(true)
---
-- @realm server
hook.Run("PlayerLoadout", ply, false)
end)
end
---
-- Called whenever a @{Player} is a forced spectator, in each server @{GM:Tick}
-- @param Player ply
-- @hook
-- @realm server
-- @see GM:PlayerDeathThink
function GM:SpectatorThink(ply)
-- when spectating a ragdoll after death
if ply:GetRagdollSpec() then
local to_switch, to_chase, to_roam = 2, 5, 8
local elapsed = CurTime() - ply.spec_ragdoll_start
local clicked = ply:KeyPressed(IN_ATTACK)
-- After first click, go into chase cam, then after another click, to into
-- eye mode. If no clicks made, go into chase after X secs, and eye mode after Y.
-- Don't switch for a second in case the player was shooting when he died,
-- this would make him accidentally switch out of ragdoll cam.
local m = ply:GetObserverMode()
if m == OBS_MODE_CHASE and clicked or elapsed > to_roam then
-- free roam mode
ply:SetRagdollSpec(false)
if ply.killerSpec and IsValid(ply.killerSpec) and ply.killerSpec:IsPlayer() and ply.killerSpec:IsActive() then
ply:Spectate(OBS_MODE_IN_EYE)
ply:SpectateEntity(ply.killerSpec)
else
ply:Spectate(OBS_MODE_ROAMING)
-- move to spectator spawn if mapper defined any
local spec_spawns = ents.FindByClass("ttt_spectator_spawn")
local spec_spawns_count = #spec_spawns
if spec_spawns_count > 0 then
local spawnPoint = spec_spawns[math.random(spec_spawns_count)]
ply:SetPos(spawnPoint:GetPos())
ply:SetEyeAngles(spawnPoint:GetAngles())
end
end
elseif m == OBS_MODE_IN_EYE and clicked and elapsed > to_switch or elapsed > to_chase then
-- start following ragdoll
ply:Spectate(OBS_MODE_CHASE)
end
if not IsValid(ply.server_ragdoll) then
ply:SetRagdollSpec(false)
end
-- when roaming and messing with ladders
elseif ply:GetMoveType() < MOVETYPE_NOCLIP and ply:GetMoveType() > 0 or ply:GetMoveType() == MOVETYPE_LADDER then
ply:Spectate(OBS_MODE_ROAMING)
end
-- when spectating a player
if ply:GetObserverMode() ~= OBS_MODE_ROAMING and not ply.propspec and not ply:GetRagdollSpec() then
local tgt = ply:GetObserverTarget()
if IsValid(tgt) and tgt:IsPlayer() then
if not tgt:IsTerror() or not tgt:Alive() then
-- stop speccing as soon as target dies
ply:Spectate(OBS_MODE_ROAMING)
ply:SpectateEntity(nil)
elseif GetRoundState() == ROUND_ACTIVE then
-- Sync position to target. Uglier than parenting, but unlike
-- parenting this is less sensitive to breakage: if we are
-- no longer spectating, we will never sync to their position.
ply:SetPos(tgt:GetPos())
end
end
end
end
---
-- Called whenever a @{Player} is a forced spectator, in each server @{GM:Tick}
-- @param Player ply
-- @hook
-- @realm server
-- @see GM:SpectatorThink
-- @function GM:PlayerDeathThink(ply)
GM.PlayerDeathThink = GM.SpectatorThink
---
-- Called when a @{Player} has been hit by a trace and damaged (such as from a bullet).
-- Returning true overrides the damage handling and prevents @{GM:ScalePlayerDamage} from being called.
-- @param Player ply The @{Player} that has been hit
-- @param DamageInfo dmginfo The damage info of the bullet
-- @param Vector dir Normalized vector direction of the bullet's path
-- @param table trace The trace of the bullet's path, see
-- <a href="https://wiki.garrysmod.com/page/Structures/TraceResult">TraceResult structure</a>
-- @return boolean Override engine handling
-- @hook
-- @realm server
-- @ref https://wiki.facepunch.com/gmod/GM:PlayerTraceAttack
-- @local
function GM:PlayerTraceAttack(ply, dmginfo, dir, trace)
if IsValid(ply.hat) and trace.HitGroup == HITGROUP_HEAD then
ply.hat:Drop(dir)
end
ply.hit_trace = trace
return false
end
---
-- Called when a @{Player} has been hurt by an explosion. Override to disable default sound effect.
-- @param Player ply @{Player} who has been hurt
-- @param DamageInfo dmginfo Damage info from explsion
-- @hook
-- @realm server
-- @ref https://wiki.facepunch.com/gmod/GM:OnDamagedByExplosion
-- @local
function GM:OnDamagedByExplosion(ply, dmginfo)
end
---
-- This hook allows you to change how much damage a @{Player} receives when one takes damage to a specific body part.
-- @note This is not called for all damage a @{Player} receives ( For example fall damage or NPC melee damage ),
-- so you should use @{GM:EntityTakeDamage} instead if you need to detect ALL damage.
-- @param Player ply The @{Player} taking damage
-- @param number hitgroup The hitgroup where the @{Player} took damage. See
-- <a href="https://wiki.garrysmod.com/page/Enums/HITGROUP">HITGROUP_Enums</a>
-- @param DamageInfo dmginfo The damage info
-- @return boolean Return true to prevent damage that this hook is called for, stop blood particle effects and blood decals.<br />
-- It is possible to return true only on client ( This will work only in multiplayer ) to stop the effects but still take damage.
-- @hook
-- @realm server
-- @ref https://wiki.facepunch.com/gmod/GM:ScalePlayerDamage
-- @local
function GM:ScalePlayerDamage(ply, hitgroup, dmginfo)
if ply:IsPlayer() and dmginfo:GetAttacker():IsPlayer() and GetRoundState() == 2 then
dmginfo:ScaleDamage(0)
end
ply.was_headshot = false
-- actual damage scaling
if hitgroup == HITGROUP_HEAD then
-- headshot if it was dealt by a bullet
ply.was_headshot = dmginfo:IsBulletDamage()
local wep = util.WeaponFromDamage(dmginfo)
if IsValid(wep) then
local s = wep:GetHeadshotMultiplier(ply, dmginfo) or 2
dmginfo:ScaleDamage(s)
end
elseif hitgroup == HITGROUP_LEFTARM
or hitgroup == HITGROUP_RIGHTARM
or hitgroup == HITGROUP_LEFTLEG
or hitgroup == HITGROUP_RIGHTLEG
or hitgroup == HITGROUP_GEAR
then
dmginfo:ScaleDamage(0.55)
end
-- Keep ignite-burn damage etc on old levels
if dmginfo:IsDamageType(DMG_DIRECT)
or dmginfo:IsExplosionDamage()
or dmginfo:IsDamageType(DMG_FALL)
or dmginfo:IsDamageType(DMG_PHYSGUN)
then
dmginfo:ScaleDamage(2)
end
end
---
-- Called when a @{Player} takes damage from falling, allows to override the damage.
-- @note The GetFallDamage hook does not get called until around 600 speed, which is a
-- rather high drop already. Hence we do our own fall damage handling in
-- OnPlayerHitGround.
-- @param Player ply
-- @param number speed
-- @return[default=0] number
-- @hook
-- @realm server
-- @ref https://wiki.facepunch.com/gmod/GM:GetFallDamage
-- @local
function GM:GetFallDamage(ply, speed)
return 0
end