-
Notifications
You must be signed in to change notification settings - Fork 0
/
nadevision.sp
1777 lines (1484 loc) · 53.9 KB
/
nadevision.sp
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
#pragma semicolon 1
#include <sourcemod>
#include <sdktools>
#include <sdkhooks>
enum ObsMode
{
OBS_MODE_NONE = 0, // not in spectator mode
OBS_MODE_DEATHCAM, // special mode for death cam animation
OBS_MODE_FREEZECAM, // zooms to a target, and freeze-frames on them
OBS_MODE_FIXED, // view from a fixed camera position
OBS_MODE_IN_EYE, // follow a player in first person view
OBS_MODE_CHASE, // follow a player in third person view
OBS_MODE_POI, // PASSTIME point of interest - game objective, big fight, anything interesting; added in the middle of the enum due to tons of hard-coded "<ROAMING" enum compares
OBS_MODE_ROAMING // free roaming
};
// sqrt(2 * sv_gravity * height) where height = 57
const float JUMP_VELOCITY = 301.993377411;
// Max player collisions in a tick.
const int MAX_PLAYER_COLLISIONS = 4;
// Player bounding box sizes.
float STAND_MIN[] = { -16.0, -16.0, 72.0 };
float STAND_MAX[] = { 16.0, 16.0, 72.0 };
float CROUCH_MIN[] = { -16.0, -16.0, 54.0 };
float CROUCH_MAX[] = { 16.0, 16.0, 54.0 };
// m_vecViewOffset Z values.
const float STAND_VIEW_HEIGHT = 64.062561;
const float CROUCH_VIEW_HEIGHT = 46.044968;
// Think interval for grenades to check for detonation.
const float NADE_THINK_TIME = 0.2;
// Time between releasing attack buttons and grenade spawning.
const float NADE_THROW_TIME = 0.1;
// The speed at which a nade is considered to be stopped by the game.
const float NADE_STOP_SPEED = 0.1;
// Max amount of time a simulated grenade can be stuck for before abandoning.
const float MAX_STUCK_TIME = 3.0;
// Grenade hull size.
const float NADE_SIZE = 2.0;
float NADE_MIN[] = { -NADE_SIZE, -NADE_SIZE, -NADE_SIZE };
float NADE_MAX[] = { NADE_SIZE, NADE_SIZE, NADE_SIZE };
// Grenade collision mask.
const int MASK_NADE = MASK_SOLID | CONTENTS_CURRENT_90;
// The highest possible surface normal Z that is not considered a floor by physics.
const float PHYS_FLOOR_Z = 0.7;
// Z offset for the starting point of inferno spawn traces.
const float MOLLY_Z_OFFSET = 10.0;
// Maximum height for molotovs/incendiaries to create an inferno.
const float MOLLY_MAX_HEIGHT = 128.0;
// Duration of smoke grenade particles.
const float SMOKE_DURATION = 20.0;
// How long it takes for a smokegrenade_projectile to be removed after detonating.
const float SMOKE_DELETE_TIME = 15.5;
// Impact cross colors, indexed by bounce count.
const int CROSS_COLOR_COUNT = 6;
int CROSS_COLORS[][4] = {
{ 255, 128, 128, 255 },
{ 255, 255, 128, 255 },
{ 128, 255, 128, 255 },
{ 128, 255, 255, 255 },
{ 128, 128, 255, 255 },
{ 255, 128, 255, 255 }
};
// Remote view camera hull size.
const float CAMERA_SIZE = 2.0;
float CAMERA_MIN[] = { -CAMERA_SIZE, -CAMERA_SIZE, -CAMERA_SIZE };
float CAMERA_MAX[] = { CAMERA_SIZE, CAMERA_SIZE, CAMERA_SIZE };
// Whether a player has nade vision disabled.
bool g_VisionDisabled[MAXPLAYERS + 1];
// Whether a player has automatic jump throws enabled.
bool g_AutoEnabled[MAXPLAYERS + 1];
// Next grenade trajectory draw time in ticks for each player.
int g_NextDrawTime[MAXPLAYERS + 1];
// Tick on which the currently thrown grenade will spawn.
int g_ReleaseTick[MAXPLAYERS + 1];
// Last CUserCmd::buttons of each player.
int g_LastButtons[MAXPLAYERS + 1];
// Whether a player has jump throw mode enabled.
bool g_JumpThrowEnabled[MAXPLAYERS + 1];
// Whether a player's IN_DUCK input should be removed until they release it.
bool g_RemoveDuck[MAXPLAYERS + 1];
// Whether a player is performing an automatic duck jump throw.
bool g_DuckJumpThrow[MAXPLAYERS + 1];
// Whether a player has nade throw info printing enabled.
bool g_NadePrintEnabled[MAXPLAYERS + 1];
// Whether a player should duckjumpthrow when crouching in jump throw mode.
bool g_DuckJumpEnabled[MAXPLAYERS + 1];
// The entity used as m_hObserverTarget when remote viewing.
int g_CameraTarget[MAXPLAYERS + 1];
// Players' eye angles before using remote viewing.
float g_OriginalEyeAngles[MAXPLAYERS + 1][3];
// Position of remote view camera.
float g_CameraPos[MAXPLAYERS + 1][3];
// Velocity of remote view camera.
float g_CameraVel[MAXPLAYERS + 1][3];
// Players' last projected grenade detonation point.
float g_LastNadeEndPos[MAXPLAYERS + 1][3];
// Materials used to draw nade trails.
int g_NadeTrailNoZ, g_NadeTrailZ;
// Timer to display help messages in chat.
Handle g_HelpTimer;
// Game cvars.
ConVar sv_cheats;
ConVar sv_gravity;
ConVar molotov_throw_detonate_time;
ConVar weapon_molotov_maxdetonateslope;
//ConVar inferno_surface_offset;
//ConVar inferno_max_range;
// Plugin cvars.
ConVar sm_nadevision;
ConVar sm_nadevision_help_timer;
ConVar sm_nadevision_debug;
ConVar sm_nadevision_ignorez;
ConVar sm_nadevision_global;
ConVar sm_nadevision_interval;
ConVar sm_nadevision_spacing;
ConVar sm_nadevision_width;
ConVar sm_nadevision_dashed;
ConVar sm_nadevision_cross_size;
ConVar sm_nadevision_cap_size;
ConVar sm_nadevision_smoke_time;
ConVar sm_nadevision_cam_height;
ConVar sm_nadevision_cam_speed;
ConVar sm_nadevision_cam_accel;
ConVar sm_nadevision_cam_decel;
ConVar sm_nadevision_cam_stopspeed;
public Plugin myinfo =
{
name = "NadeVision",
author = "Altimor",
description = "Displays predicted grenade trajectory.",
version = "2.5.4",
url = ""
};
const int NADE_NAME_LEN = 32;
enum struct NadeType
{
char className[NADE_NAME_LEN]; // weapon_*
char shortName[NADE_NAME_LEN]; // Used for cvar names.
char longName[NADE_NAME_LEN]; // Used for cvar descriptions.
// A fuseTime of 0 means the grenade detonates when its speed falls below 1 in/s.
float fuseTime; // Natural detonation time, accurate to a 200ms Think.
bool isFire; // Molotov/Incendiary.
bool stopDetonate; // Smoke/Decoy.
int color[4]; // Cached color cvar values.
ConVar cvColor; // Color of trail segments and ring.
ConVar cvFadeTime; // Time for trail segments to linger after the nade passes them.
ConVar cvRingTime; // Time for the ring to linger after the nade detonates.
ConVar cvRingSize; // Size of detonation ring.
void Init(
const char[] className,
const char[] shortName,
const char[] longName,
float fuseTime,
bool isFire,
bool stopDetonate,
int colorR,
int colorG,
int colorB,
int colorA,
float fadeTime,
float ringTime,
float ringSize)
{
strcopy(this.className, NADE_NAME_LEN, className);
strcopy(this.shortName, NADE_NAME_LEN, shortName);
strcopy(this.longName, NADE_NAME_LEN, longName);
this.fuseTime = fuseTime;
this.isFire = isFire;
this.stopDetonate = stopDetonate;
const int NAME_LEN = 64, DESC_LEN = 256, VALUE_LEN = 32;
char name[NAME_LEN], desc[DESC_LEN], value[VALUE_LEN];
Format(name, NAME_LEN, "sm_nadevision_%s_color", this.shortName);
Format(desc, DESC_LEN, "Color of the %s's predicted path.", this.longName);
Format(value, VALUE_LEN, "%i %i %i %i", colorR, colorG, colorB, colorA);
this.cvColor = CreateConVar(name, value, desc, FCVAR_NOTIFY | FCVAR_DONTRECORD);
this.cvColor.AddChangeHook(OnNadeColorChanged);
// Immediately update the cached colors since the cvar may already exist.
this.cvColor.GetString(value, VALUE_LEN);
this.UpdateColor(value);
Format(name, NAME_LEN, "sm_nadevision_%s_fade_time", this.shortName);
Format(desc, DESC_LEN, "Number of seconds for trail segments to linger after the %s reaches them.", this.longName);
Format(value, VALUE_LEN, "%f", fadeTime);
this.cvFadeTime = CreateConVar(name, value, desc, FCVAR_NOTIFY | FCVAR_DONTRECORD, true, 0.0);
Format(name, NAME_LEN, "sm_nadevision_%s_ring_time", this.shortName);
Format(desc, DESC_LEN, "Number of seconds for the ring to linger after the %s detonates.", this.longName);
Format(value, VALUE_LEN, "%f", ringTime);
this.cvRingTime = CreateConVar(name, value, desc, FCVAR_NOTIFY | FCVAR_DONTRECORD, true, 0.0);
Format(name, NAME_LEN, "sm_nadevision_%s_ring_size", this.shortName);
Format(desc, DESC_LEN, "Size of the ring at the %s's detonation point.", this.longName);
Format(value, VALUE_LEN, "%f", ringSize);
this.cvRingSize = CreateConVar(name, value, desc, FCVAR_NOTIFY | FCVAR_DONTRECORD, true, 0.0);
}
void GetFuseTime(float &result)
{
if (this.isFire)
result = molotov_throw_detonate_time.FloatValue;
else
result = this.fuseTime;
}
void GetColor(int color[4])
{
color[0] = this.color[0];
color[1] = this.color[1];
color[2] = this.color[2];
color[3] = this.color[3];
}
// Update the cached color values.
void UpdateColor(const char[] newValue)
{
char components[4][16];
ExplodeString(newValue, " ", components, 4, 16);
bool clamped = false;
for (int i = 0; i < 4; i++)
{
int value = StringToInt(components[i]);
if (value >= 0 && value <= 255)
{
this.color[i] = value;
continue;
}
this.color[i] = value < 0 ? 0 : 255;
clamped = true;
}
if (!clamped)
return;
// Update the cvar with the clamped values.
char newString[64];
Format(newString, 64, "%i %i %i %i", this.color[0], this.color[1], this.color[2], this.color[3]);
this.cvColor.SetString(newString);
}
};
enum NadeTypeId
{
NADE_HE = 0,
NADE_Flash = 1,
NADE_Smoke = 2,
NADE_Molly = 3,
NADE_Inc = 4,
NADE_Decoy = 5
};
const int NADE_Max = 6;
NadeType g_Nades[NadeTypeId];
public void OnPluginStart()
{
if (GetEngineVersion() != Engine_CSGO)
SetFailState("NadeVision is incompatible with games other than CSGO.");
sv_cheats = FindConVar("sv_cheats");
sv_gravity = FindConVar("sv_gravity");
molotov_throw_detonate_time = FindConVar("molotov_throw_detonate_time");
weapon_molotov_maxdetonateslope = FindConVar("weapon_molotov_maxdetonateslope");
//inferno_surface_offset = FindConVar("inferno_surface_offset");
//inferno_max_range = FindConVar("inferno_max_range");
sm_nadevision = CreateConVar("sm_nadevision", "1", "Enable grenade trajectory prediction.", FCVAR_NOTIFY | FCVAR_DONTRECORD, true, 0.0, true, 1.0);
sm_nadevision_help_timer = CreateConVar("sm_nadevision_help_timer", "120", "Time in between global help messages. 0 disables.", FCVAR_NOTIFY | FCVAR_DONTRECORD, true, 0.0);
sm_nadevision_debug = CreateConVar("sm_nadevision_debug", "0", "Enable debug spew for grenade simulation.", FCVAR_NOTIFY | FCVAR_DONTRECORD, true, 0.0, true, 1.0);
sm_nadevision_ignorez = CreateConVar("sm_nadevision_ignorez", "1", "Whether to draw grenade trails through walls.", FCVAR_NOTIFY | FCVAR_DONTRECORD, true, 0.0, true, 1.0);
sm_nadevision_global = CreateConVar("sm_nadevision_global", "0", "If enabled, trails will be visible to all players.", FCVAR_NOTIFY | FCVAR_DONTRECORD, true, 0.0, true, 1.0);
sm_nadevision_interval = CreateConVar("sm_nadevision_interval", "0.078125", "Interval between grenade trail updates.", FCVAR_NOTIFY | FCVAR_DONTRECORD, true, 0.0);
sm_nadevision_spacing = CreateConVar("sm_nadevision_spacing", "10", "Minimum distance between TE beams in trails.", FCVAR_NOTIFY | FCVAR_DONTRECORD, true, 0.0);
sm_nadevision_width = CreateConVar("sm_nadevision_width", "0.5", "Width of grenade trajectory trail.", FCVAR_NOTIFY | FCVAR_DONTRECORD, true, 0.0);
sm_nadevision_dashed = CreateConVar("sm_nadevision_dashed", "1", "Whether the grenade trail should be dashed or solid.", FCVAR_NOTIFY | FCVAR_DONTRECORD, true, 0.0, true, 1.0);
sm_nadevision_cross_size = CreateConVar("sm_nadevision_cross_size", "5", "Impact cross size in distance from center per axis.", FCVAR_NOTIFY | FCVAR_DONTRECORD, true, 0.0);
sm_nadevision_cap_size = CreateConVar("sm_nadevision_cap_size", "0.25", "Size of the caps at the beginning and end of the trail.", FCVAR_NOTIFY | FCVAR_DONTRECORD, true, 0.0);
sm_nadevision_smoke_time = CreateConVar("sm_nadevision_smoke_time", "20", "Adjusts the duration that smoke grenade particles appear for.", FCVAR_NOTIFY | FCVAR_DONTRECORD, true, 0.0);
sm_nadevision_cam_height = CreateConVar("sm_nadevision_cam_height", "24", "Adjusts the default height of the remote view camera.", FCVAR_NOTIFY | FCVAR_DONTRECORD, true, 0.0);
sm_nadevision_cam_speed = CreateConVar("sm_nadevision_cam_speed", "700", "Adjusts the max speed of the remote view camera.", FCVAR_NOTIFY | FCVAR_DONTRECORD, true, 0.0);
sm_nadevision_cam_accel = CreateConVar("sm_nadevision_cam_accel", "0.7", "Adjusts the accel per unit of speed per second of the remote view camera.", FCVAR_NOTIFY | FCVAR_DONTRECORD, true, 0.0);
sm_nadevision_cam_decel = CreateConVar("sm_nadevision_cam_decel", "2.0", "Adjusts the decel per unit of speed per second of the remote view camera.", FCVAR_NOTIFY | FCVAR_DONTRECORD, true, 0.0);
sm_nadevision_cam_stopspeed = CreateConVar("sm_nadevision_cam_stopspeed", "150", "Adjusts the minimum speed for speed based deceleration scaling.", FCVAR_NOTIFY | FCVAR_DONTRECORD, true, 0.0);
g_HelpTimer = CreateTimer(sm_nadevision_help_timer.FloatValue, HelpTimer, _, TIMER_REPEAT);
sm_nadevision_help_timer.AddChangeHook(OnHelpTimerChanged);
AddCommandListener(BlockSpecMode, "spec_mode");
g_Nades[NADE_HE].Init("weapon_hegrenade", "he", "H.E. grenade", 1.5, false, false, 255, 128, 0, 255, 3.0, 3.0, 64.0);
g_Nades[NADE_Flash].Init("weapon_flashbang", "flash", "flashbang", 1.5, false, false, 0, 255, 0, 255, 3.0, 3.0, 30.0);
g_Nades[NADE_Smoke].Init("weapon_smokegrenade", "smoke", "smoke grenade", 1.5, false, true, 0, 255, 255, 255, 5.0, 17.5, 250.0);
g_Nades[NADE_Molly].Init("weapon_molotov", "molly", "molotov", 0.0, true, false, 255, 0, 0, 255, 3.0, 7.0, 120.0);
g_Nades[NADE_Inc].Init("weapon_incgrenade", "inc", "incendiary", 0.0, true, false, 255, 0, 0, 255, 3.0, 7.0, 120.0);
g_Nades[NADE_Decoy].Init("weapon_decoy", "decoy", "decoy grenade", 3.0, false, true, 255, 0, 255, 255, 5.0, 17.5, 250.0);
AutoExecConfig(true, "nadevision");
}
public void OnMapStart()
{
g_NadeTrailNoZ = PrecacheModel("materials/vgui/white.vmt");
g_NadeTrailZ = PrecacheModel("materials/debug/debugvertexcolor.vmt");
}
public void OnClientPutInServer(int client)
{
// Initialize per client globals.
g_VisionDisabled[client] = false;
g_AutoEnabled[client] = false;
g_NextDrawTime[client] = 0;
g_ReleaseTick[client] = 0;
g_JumpThrowEnabled[client] = false;
g_LastButtons[client] = 0;
g_DuckJumpThrow[client] = false;
g_NadePrintEnabled[client] = true;
g_DuckJumpEnabled[client] = false;
g_CameraTarget[client] = 0;
}
public void OnClientDisconnect(int client)
{
// Remove a client's freelook target when they disconnect.
int target = g_CameraTarget[client];
if (target != 0)
RemoveEntity(target);
g_CameraTarget[client] = 0;
}
public Action OnClientSayCommand(int client, const char[] command, const char[] sArgs)
{
if (StrEqual(sArgs, ".nadevision") || StrEqual(sArgs, ".altimor"))
{
PrintToChat(client, " \x10[NadeVision]\x01 Prime a grenade to view its trajectory.");
PrintToChat(client, " \x10[NadeVision]\x01 Type \x04.shownades\x01 to toggle trajectory display.");
PrintToChat(client, " \x10[NadeVision]\x01 Type \x04.nadeprint\x01 to toggle throw info console printing.");
if (sv_cheats.BoolValue)
{
PrintToChat(client, " \x10[NadeVision]\x01 If you can't see the entire nade trajectory, it may help to");
PrintToChat(client, "use \x0Csv_force_transmit_ents 1\x01 with \x0Cr_novis 1\x01 or \x0Cr_lockpvs 1\x01.");
}
PrintToChat(client, " \x10[NadeVision]\x01 Press \x02USE\x01 while priming to toggle jump throw mode.");
PrintToChat(client, "Jump throws will be predicted and can be automated.");
PrintToChat(client, " \x10[NadeVision]\x01 Press \x02RELOAD\x01 while priming to toggle remote viewing.");
PrintToChat(client, "A controllable remote camera will be created at the detonation point and you will be frozen in place.");
PrintToChat(client, " \x10[NadeVision]\x01 Type \x04.autothrow\x01 to toggle automatic jump throws.");
PrintToChat(client, " \x10[NadeVision]\x01 Type \x04.duckjump\x01 to enable duck jump throws.");
PrintToChat(client, "Holding \x02DUCK\x01 while in jump throw mode will simulate");
PrintToChat(client, "inputting duck+jump on the same tick and releasing duck before");
PrintToChat(client, "the grenade is spawned.");
return Plugin_Handled;
}
else if (StrEqual(sArgs, ".shownades"))
{
bool enabled = !(g_VisionDisabled[client] = !g_VisionDisabled[client]);
PrintToChat(client, " \x10[NadeVision]\x01 Trajectory display %s\x01.", enabled ? "\x04ON" : "\x02OFF");
return Plugin_Handled;
}
else if (StrEqual(sArgs, ".nadeprint"))
{
bool enabled = g_NadePrintEnabled[client] = !g_NadePrintEnabled[client];
PrintToChat(client, " \x10[NadeVision]\x01 Throw info printing %s\x01.", enabled ? "\x04ON" : "\x02OFF");
return Plugin_Handled;
}
else if (StrEqual(sArgs, ".autothrow"))
{
bool enabled = g_AutoEnabled[client] = !g_AutoEnabled[client];
PrintToChat(client, " \x10[NadeVision]\x01 Automatic jump throws %s\x01.", enabled ? "\x04ON" : "\x02OFF");
return Plugin_Handled;
}
else if (StrEqual(sArgs, ".duckjump"))
{
bool enabled = g_DuckJumpEnabled[client] = !g_DuckJumpEnabled[client];
PrintToChat(client, " \x10[NadeVision]\x01 Duck jump throw %s\x01.", enabled ? "\x04ON" : "\x02OFF");
if (enabled)
PrintToChat(client, "Hold \x02DUCK\x01 while in jump throw mode to use.");
return Plugin_Handled;
}
return Plugin_Continue;
}
// Hook smoke grenades' ThinkPost to set the fade time on detonation.
public void OnEntityCreated(int entity, const char[] classname)
{
if (!StrEqual(classname, "smokegrenade_projectile"))
return;
SDKHook(entity, SDKHook_ThinkPost, SmokeThinkPost);
}
// Set the fade time on smokes.
void SmokeThinkPost(int entity)
{
if (!GetEntProp(entity, Prop_Send, "m_bDidSmokeEffect"))
return;
// Change m_nSmokeEffectTickBegin based on the difference between the
// normal smoke duration and the cvar.
float timeDifference = sm_nadevision_smoke_time.FloatValue - SMOKE_DURATION;
if (timeDifference != 0.0)
{
int serverTick = RoundToNearest(GetGameTime() / GetTickInterval());
int newBegin = serverTick + RoundToNearest(timeDifference / GetTickInterval());
SetEntProp(entity, Prop_Send, "m_nSmokeEffectTickBegin", newBegin);
// Adjust the smokegrenade_projectile deletion time.
// This controls the grey overlay when walking through the smoke.
float deleteTime = timeDifference + SMOKE_DELETE_TIME;
CreateTimer(deleteTime, RemoveSmoke, entity);
}
SDKUnhook(entity, SDKHook_ThinkPost, SmokeThinkPost);
}
Action RemoveSmoke(Handle timer, int entity)
{
RemoveEntity(entity);
}
// Block spec_mode when in remote viewing mode.
Action BlockSpecMode(int client, const char[] command, int argc)
{
return g_CameraTarget[client] != 0 ? Plugin_Handled : Plugin_Continue;
}
// Set the cached color values for a nade when the cvar is changed.
void OnNadeColorChanged(ConVar convar, const char[] oldValue, const char[] newValue)
{
for (int type = 0; type < NADE_Max; type++)
{
if (g_Nades[type].cvColor == convar)
{
g_Nades[type].UpdateColor(newValue);
return;
}
}
}
Action HelpTimer(Handle timer, any data)
{
PrintToChatAll(" \x10[NadeVision]\x01 Type \x04.nadevision\x01 or \x04.altimor\x01 for help.");
return Plugin_Continue;
}
void OnHelpTimerChanged(ConVar convar, const char[] oldValue, const char[] newValue)
{
if (g_HelpTimer != null)
{
g_HelpTimer.Close();
g_HelpTimer = null;
}
if (convar.FloatValue > 0.0)
g_HelpTimer = CreateTimer(convar.FloatValue, HelpTimer, _, TIMER_REPEAT);
}
// Custom GetClientEyeAngles that retrieves the original eye angles when remote viewing.
void NadeGetClientEyeAngles(int client, float angles[3])
{
if (g_CameraTarget[client] == 0)
GetClientEyeAngles(client, angles);
else
angles = g_OriginalEyeAngles[client];
}
// Filter out all players and grenade projectiles.
bool TraceFilter(int entity, int contentsMask, int data)
{
if (entity >= 1 || entity <= MaxClients)
return false;
char classname[256];
if (!GetEntityClassname(entity, classname, 256))
return true;
return !StrEqual(classname, "hegrenade_projectile")
&& !StrEqual(classname, "flashbang_projectile")
&& !StrEqual(classname, "smokegrenade_projectile")
&& !StrEqual(classname, "molotov_projectile")
&& !StrEqual(classname, "decoy_projectile");
}
// Perform a collision trace for a grenade.
Handle TraceGrenade(const float start[3], const float end[3])
{
return TR_TraceHullFilterEx(
start,
end,
NADE_MIN,
NADE_MAX,
MASK_NADE,
TraceFilter);
}
// Perform a collision trace for the remote view camera.
Handle TraceCamera(const float start[3], const float end[3])
{
return TR_TraceHullFilterEx(
start,
end,
CAMERA_MIN,
CAMERA_MAX,
MASK_SOLID,
TraceFilter);
}
// Perform a collision trace for a player.
Handle TracePlayer(const float start[3], const float end[3], bool ducking)
{
return TR_TraceHullFilterEx(
start,
end,
ducking ? CROUCH_MIN : STAND_MIN,
ducking ? CROUCH_MAX : STAND_MAX,
MASK_PLAYERSOLID,
TraceFilter);
}
public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float move[3], float viewangles[3])
{
int lastButtons = g_LastButtons[client];
int realButtons = g_LastButtons[client] = buttons;
bool duckJumpThrow = g_DuckJumpThrow[client];
if (!(buttons & IN_DUCK))
g_RemoveDuck[client] = false;
else if (g_RemoveDuck[client])
buttons &= ~IN_DUCK;
UpdateDuckJumpThrowProgress(client, buttons, lastButtons);
if (!sm_nadevision.BoolValue || g_VisionDisabled[client])
{
RestoreCamera(client, viewangles);
return Plugin_Continue;
}
int weapon = GetEntPropEnt(client, Prop_Data, "m_hActiveWeapon");
if (weapon == -1)
{
RestoreCamera(client, viewangles);
return Plugin_Continue;
}
// Use the corrected client clock.
int tickBase = GetEntProp(client, Prop_Send, "m_nTickBase");
// Get grenade attributes.
NadeTypeId nadeTypeId;
if (!InitGrenade(client, weapon, tickBase, nadeTypeId))
{
RestoreCamera(client, viewangles);
return Plugin_Continue;
}
// Let the player toggle jumpthrow mode and use auto-jumpthrow.
if (!duckJumpThrow)
UpdateJumpThrow(client, buttons, realButtons, lastButtons, tickBase);
// Check if the player wants to remote view the detonation point.
bool inRemoteView = false;
bool pressedReload = (realButtons & IN_RELOAD) && !(lastButtons & IN_RELOAD);
if (g_CameraTarget[client] != 0)
{
if (pressedReload)
{
RestoreCamera(client, viewangles);
}
else
{
// Continue updating m_hObserverTarget to ensure it isn't overwritten on the client.
SetEntPropEnt(client, Prop_Send, "m_hObserverTarget", g_CameraTarget[client]);
RemoteViewMove(client, buttons, move, viewangles);
inRemoteView = true;
}
}
else if (pressedReload)
{
RemoteView(client, g_LastNadeEndPos[client]);
RemoteViewMove(client, buttons, move, viewangles);
inRemoteView = true;
}
if (inRemoteView)
{
// Make them keep holding attack buttons since there's no way to
// +attack or +attack2 while observing.
int lastAttack = lastButtons & (IN_ATTACK | IN_ATTACK2);
buttons |= lastAttack;
g_LastButtons[client] |= lastAttack;
}
bool throwing, releasing;
if (tickBase <= g_ReleaseTick[client])
{
// Already throwing.
throwing = false;
releasing = tickBase == g_ReleaseTick[client];
}
else
{
throwing = (buttons & (IN_ATTACK | IN_ATTACK2)) == 0;
releasing = false;
if (throwing)
{
int throwTicks = RoundToFloor(NADE_THROW_TIME / GetTickInterval()) + 2;
g_ReleaseTick[client] = tickBase + throwTicks;
}
}
// Check if it's time to redraw trails.
if (tickBase < g_NextDrawTime[client] && !releasing)
return Plugin_Continue;
// Update the next draw time.
int drawTicks = RoundToCeil(sm_nadevision_interval.FloatValue / GetTickInterval());
g_NextDrawTime[client] = tickBase + drawTicks + 1;
float nadePos[3], nadeVel[3];
CalcGrenadeSpawn(client, weapon, realButtons, nadePos, nadeVel, tickBase);
if (sm_nadevision_debug.BoolValue)
{
float eyePos[3];
GetClientEyePosition(client, eyePos);
PrintToConsole(client, "--- Grenade Start");
PrintToConsole(client, "Weapon Entity %i", weapon);
PrintToConsole(client, "Grenade Type %i", g_Nades[nadeTypeId].longName);
PrintToConsole(client, "Initial Position %f %f %f", nadePos[0], nadePos[1], nadePos[2]);
PrintToConsole(client, "Initial Distance %f", GetVectorDistance(eyePos, nadePos));
PrintToConsole(client, "Initial Velocity %f %f %f", nadeVel[0], nadeVel[1], nadeVel[2]);
PrintToConsole(client, "Initial Speed %f", GetVectorLength(nadeVel));
PrintToConsole(client, "Throwing %s", throwing ? "Yes" : "No");
PrintToConsole(client, "Releasing %s", releasing ? "Yes" : "No");
}
float travelTime = DrawGrenadeTrail(
client,
g_Nades[nadeTypeId],
nadePos,
nadeVel,
releasing,
tickBase);
g_LastNadeEndPos[client] = nadePos;
if (releasing && g_NadePrintEnabled[client])
{
// Print grenade throw information.
float origin[3], angles[3], playerVel[3];
GetClientAbsOrigin(client, origin);
NadeGetClientEyeAngles(client, angles);
GetEntPropVector(client, Prop_Data, "m_vecAbsVelocity", playerVel);
float delta[3];
SubtractVectors(origin, nadePos, delta);
float dist2d = SquareRoot(delta[0] * delta[0] + delta[1] * delta[1]);
float speed = SquareRoot(playerVel[0] * playerVel[0] + playerVel[1] * playerVel[1]);
PrintToConsole(client, "--- Throw at t=%f", GetGameTime());
PrintToConsole(client, "Player Pos %f %f %f", origin[0], origin[1], origin[2]);
PrintToConsole(client, "Player Vel %f %f %f", playerVel[0], playerVel[1], playerVel[2]);
PrintToConsole(client, "Player Spd %f", speed);
PrintToConsole(client, "Eye Angles %f %f %f", angles[0], angles[1], angles[2]);
PrintToConsole(client, "End Point %f %f %f", nadePos[0], nadePos[1], nadePos[2]);
PrintToConsole(client, "2D Distance %f", dist2d);
PrintToConsole(client, "Height Diff %f", nadePos[2] - origin[2]);
PrintToConsole(client, "Travel Time %f", travelTime);
PrintToConsole(client, "Strength %f", GetEntPropFloat(weapon, Prop_Send, "m_flThrowStrength"));
PrintToConsole(client, "setpos_exact %f %f %f; setang %f %f %f",
origin[0], origin[1], origin[2],
angles[0], angles[1], angles[2]);
}
return Plugin_Continue;
}
// If a camera target was created for this player, restore their view and delete the target.
void RestoreCamera(int client, float viewangles[3])
{
int target = g_CameraTarget[client];
if (g_CameraTarget[client] == 0)
return;
TeleportEntity(client, NULL_VECTOR, g_OriginalEyeAngles[client], NULL_VECTOR);
viewangles = g_OriginalEyeAngles[client];
// Remove the target and the parent created to force transmission.
int parent = GetEntPropEnt(target, Prop_Data, "m_hParent");
RemoveEntity(parent);
RemoveEntity(target);
SetEntProp(client, Prop_Data, "m_MoveType", MOVETYPE_WALK);
SetEntProp(client, Prop_Send, "m_iObserverMode", OBS_MODE_NONE);
SetEntPropEnt(client, Prop_Send, "m_hObserverTarget", -1);
g_CameraTarget[client] = 0;
}
// Allow a client to remotely view a position.
void RemoteView(int client, const float pos[3])
{
// Move a bit above the position.
float camPos[3];
camPos[0] = pos[0];
camPos[1] = pos[1];
camPos[2] = pos[2] + sm_nadevision_cam_height.FloatValue;
// Check camera collision.
Handle trace = TraceCamera(pos, camPos);
TR_GetEndPosition(camPos, trace);
trace.Close();
GetClientEyeAngles(client, g_OriginalEyeAngles[client]);
g_CameraVel[client][0] = g_CameraVel[client][1] = g_CameraVel[client][2] = 0.0;
// Create a new camera target for this client.
//
// This is a hack for this:
//
// // If our target isn't visible, we're at a camera point of some kind.
// // Instead of letting the player rotate around an invisible point, treat
// // the point as a fixed camera.
// if ( !target->GetBaseAnimating() && !target->GetModel() )
int target = CreateEntityByName("prop_dynamic");
DispatchKeyValue(target, "model", "models/editor/axis_helper_thick.mdl");
DispatchKeyValue(target, "solid", "0");
DispatchKeyValueFloat(target, "modelscale", 0.54);
DispatchKeyValueVector(target, "origin", camPos);
SDKHook(target, SDKHook_SetTransmit, RemoteViewTransmit);
DispatchSpawn(target);
// Force transmission with an FL_EDICT_ALWAYS parent.
int parent = CreateEntityByName("info_target");
SetEdictFlags(parent, GetEdictFlags(parent) | FL_EDICT_ALWAYS);
DispatchSpawn(parent);
SetVariantString("!activator");
AcceptEntityInput(target, "SetParent", parent);
SetEntProp(client, Prop_Data, "m_MoveType", MOVETYPE_NONE);
SetEntProp(client, Prop_Send, "m_iObserverMode", OBS_MODE_CHASE);
SetEntPropEnt(client, Prop_Send, "m_hObserverTarget", target);
g_CameraTarget[client] = target;
g_CameraPos[client] = camPos;
}
Action RemoteViewTransmit(int entity, int client)
{
return g_CameraTarget[client] == entity ? Plugin_Continue : Plugin_Handled;
}
void RemoteViewAccelerate(int client, int buttons, const float move[3], const float viewangles[3])
{
float velocity[3];
float speed = GetVectorLength(move);
velocity = g_CameraVel[client];
if (speed == 0.0)
{
// Not moving, just decelerate.
float decel = sm_nadevision_cam_decel.FloatValue * GetTickInterval();
float length = GetVectorLength(velocity);
float stopSpeed = sm_nadevision_cam_stopspeed.FloatValue;
if (length > stopSpeed)
decel *= length;
else
decel *= stopSpeed;
if (decel >= length)
velocity[0] = velocity[1] = velocity[2] = 0.0;
else
ScaleVector(velocity, (length - decel) / length);
g_CameraVel[client] = velocity;
}
// Cap input vector speed to the cvar and allow walking.
float maxSpeed = sm_nadevision_cam_speed.FloatValue;
if (buttons & IN_SPEED)
maxSpeed /= 3.0;
// Rescale the input vector.
speed *= maxSpeed / 450.0;
if (speed > maxSpeed)
speed = maxSpeed;
// Rotate input vector based on viewangles.
float moveFwd[3], moveRight[3], moveUp[3], moveDir[3];
GetAngleVectors(viewangles, moveFwd, moveRight, moveUp);
ScaleVector(moveFwd, move[0]);
ScaleVector(moveRight, move[1]);
ScaleVector(moveUp, move[2]);
AddVectors(moveFwd, moveRight, moveDir);
AddVectors(moveDir, moveUp, moveDir);
NormalizeVector(moveDir, moveDir);
// Split velocity into two components facing towards and away
// from the target direction.
float towardComponent[3], awayComponent[3];
float moveDot = GetVectorDotProduct(velocity, moveDir);
if (moveDot >= speed)
moveDot = speed;
if (moveDot <= 0.0)
{
towardComponent[0] = towardComponent[1] = towardComponent[2] = 0.0;
}
else
{
towardComponent = moveDir;
ScaleVector(towardComponent, moveDot);
}
SubtractVectors(velocity, towardComponent, awayComponent);
// Accelerate from player movement.
float accelVec[3];
float accel = sm_nadevision_cam_accel.FloatValue * speed * GetTickInterval();
accelVec = moveDir;
if (moveDot + accel >= speed)
ScaleVector(accelVec, speed - moveDot);
else
ScaleVector(accelVec, accel);
AddVectors(towardComponent, accelVec, towardComponent);
// Decelerate if player isn't moving in this direction.
float decel = sm_nadevision_cam_decel.FloatValue * GetTickInterval();
float awayLength = GetVectorLength(awayComponent);
float stopSpeed = sm_nadevision_cam_stopspeed.FloatValue;
if (awayLength > stopSpeed)
decel *= awayLength;
else
decel *= stopSpeed;
if (decel >= awayLength)
awayComponent[0] = awayComponent[1] = awayComponent[2] = 0.0;
else
ScaleVector(awayComponent, (awayLength - decel) / awayLength);
// Update velocity.
AddVectors(towardComponent, awayComponent, g_CameraVel[client]);
}
void RemoteViewMove(int client, int buttons, const float move[3], const float viewangles[3])
{
RemoteViewAccelerate(client, buttons, move, viewangles);
float startPos[3];
startPos = g_CameraPos[client];
float velocity[3];
velocity = g_CameraVel[client];
// Collide and slide.
float endPos[3];
float timeSlice = 1.0;
for (int i = 0; i < MAX_PLAYER_COLLISIONS; i++)
{
float delta[3];
delta = velocity;
ScaleVector(delta, GetTickInterval() * timeSlice);
float deltaLength = GetVectorLength(delta);
if (deltaLength == 0.0)
{
endPos = startPos;
break;
}
AddVectors(startPos, delta, endPos);
// Start 10 units back to avoid clipping through displacements.
float newStartPos[3];
NormalizeVector(delta, newStartPos);
ScaleVector(newStartPos, -10.0);
AddVectors(newStartPos, startPos, newStartPos);
Handle trace = TraceCamera(startPos, newStartPos);
float startPosDelta = TR_GetFraction(trace) * 10.0;
TR_GetEndPosition(newStartPos, trace);
trace.Close();
trace = TraceCamera(newStartPos, endPos);
float totalLength = deltaLength + startPosDelta;
float fraction = (TR_GetFraction(trace) * totalLength - startPosDelta) / deltaLength;
if (!TR_DidHit(trace))
{
trace.Close();
break;
}
// Clip velocity.
float impulse[3];
TR_GetPlaneNormal(trace, impulse);
ScaleVector(impulse, GetVectorDotProduct(velocity, impulse));
SubtractVectors(velocity, impulse, velocity);
// Move for the rest of the tick.
if (fraction > 0.0)
{
timeSlice *= 1.0 - fraction;
TR_GetEndPosition(startPos, trace);
}
trace.Close();
}
g_CameraVel[client] = velocity;
g_CameraPos[client] = endPos;
TeleportEntity(g_CameraTarget[client], endPos, NULL_VECTOR, NULL_VECTOR);
}
// Allow a player to toggle jumpthrow mode with IN_USE.
// Automatically jumpthrow and duckjumpthrow when in jumpthrow mode.
void UpdateJumpThrow(int client, int &buttons, int realButtons, int lastButtons, int tickBase)
{
bool enabled = g_JumpThrowEnabled[client];
if ((realButtons & IN_USE) && !(lastButtons & IN_USE))
{
g_JumpThrowEnabled[client] = enabled = !enabled;
PrintHintText(client, "[NadeVision] Jump throw %s", enabled ? "ON" : "OFF");
}
if (!g_AutoEnabled[client])
return;
// Check if already throwing.
if (tickBase < g_ReleaseTick[client])
return;
int lastAttack = lastButtons & (IN_ATTACK | IN_ATTACK2);
// Check if jumpthrowing.
if (!enabled || (buttons & (IN_ATTACK | IN_ATTACK2)))
return;
if (!(realButtons & IN_DUCK) || !g_DuckJumpEnabled[client])