-
Notifications
You must be signed in to change notification settings - Fork 0
/
drzed.sp
3960 lines (3808 loc) · 177 KB
/
drzed.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
#include <sourcemod>
#include <sdkhooks>
#include <cstrike>
#include <sdktools>
#pragma newdecls required
#pragma semicolon 1
public Plugin myinfo =
{
name = "Dr Zed",
author = "Chris Angelico",
description = "Dr Zed: I maintain the med vendors",
version = "0.99",
url = "https://github.com/Rosuav/TF2BuffBot",
};
//Note: To regenerate netprops.txt, go into the server and run: sm_dump_netprops netprops.txt
//then excise uninteresting data with: sed -i 's/(offset [0-9]*) //' steamcmd_linux/csgo/csgo/netprops.txt
ConVar sm_drzed_max_hitpoints = null; //(0) Number of hitpoints a normal character has (w/o Assault Suit) - 0 to leave at default
ConVar sm_drzed_heal_max = null; //(0) If nonzero, healing can be bought up to that many hitpoints (100 is normal maximum)
ConVar sm_drzed_heal_price = null; //(0) If nonzero, healing can be bought for that much money
ConVar sm_drzed_heal_freq_flyer = null; //(0) Every successful purchase of healing adds this to your max health
ConVar sm_drzed_heal_cooldown = null; //(15) After buying healing, you can't buy more for this many seconds.
ConVar sm_drzed_heal_damage_cd = null; //(2.5) Healing is available only when you've been out of combat for X seconds (taking no damage).
ConVar sm_drzed_suit_health_bonus = null; //(0) Additional HP gained when you equip the Heavy Assault Suit (also buffs heal_max while worn)
ConVar sm_drzed_gate_health_left = null; //(0) If nonzero, one-shots from full health will leave you on this much health
ConVar sm_drzed_gate_overkill = null; //(200) One-shots of at least this much damage (after armor) ignore the health gate
ConVar sm_drzed_crippled_health = null; //(0) If >0, you get this many hitpoints of extra health during which you're crippled.
ConVar sm_drzed_crippled_revive_count = null; //(4) When someone has been crippled, it takes this many knife slashes to revive them.
ConVar sm_drzed_crippled_speed = null; //(50) A crippled person moves no faster than this (knife = 250, Negev = 150, scoped AWP = 100)
ConVar sm_drzed_max_anarchy = null; //(0) Maximum Anarchy stacks - 0 to disable anarchy
ConVar sm_drzed_anarchy_bonus = null; //(5) Percent bonus to damage per anarchy stack. There's no accuracy penalty though.
ConVar sm_drzed_anarchy_kept_on_death = null; //(0) Percentage of anarchy stacks saved on death (rounded down).
ConVar sm_drzed_anarchy_per_kill = null; //(0) Whether you gain anarchy for getting a kill
ConVar sm_drzed_hack = null; //(0) Activate some coded hack - actual meaning may change. Used for rapid development.
ConVar sm_drzed_allow_recall = null; //(0) Set to 1 to enable !recall and !recall2.
ConVar sm_drzed_admin_chat_name = null; //("") Name of admin for chat purposes
ConVar bot_autobuy_nades = null; //(1) Bots will buy more grenades than they otherwise might
ConVar bots_get_empty_weapon = null; //("") Give bots an ammo-less weapon on startup (eg weapon_glock). Use only if they wouldn't get a weapon in that slot.
ConVar damage_scale_humans = null; //(1.0) Scale all damage dealt by humans
ConVar damage_scale_bots = null; //(1.0) Scale all damage dealt by bots
ConVar learn_smoke = null; //(0) Show information on smoke throws and where they pop
ConVar learn_stutterstep = null; //(0) Show information on each shot fired to help you master stutter-stepping
ConVar bomb_defusal_puzzles = null; //(0) Issue this many puzzles before allowing the bomb to be defused (can't be changed during a round)
ConVar insta_respawn_damage_lag = null; //(0) Instantly respawn on death, with this many seconds of damage immunity and inability to fire
ConVar guardian_underdome_waves = null; //(0) Utilize Underdome rules
ConVar limit_fire_rate = null; //(0) If nonzero, guns cannot fire faster than N rounds/minute; if 1, will show fire rate each shot.
ConVar autosmoke_pitch_min = null; //("0.0") Hold +alt1 to autothrow smokes
ConVar autosmoke_pitch_max = null; //("0.0") Hold +alt1 to autothrow smokes
ConVar autosmoke_pitch_delta = null; //(0.0) Hold +alt1 to autothrow smokes
ConVar autosmoke_yaw_min = null; //("0.0") Hold +alt1 to autothrow smokes
ConVar autosmoke_yaw_max = null; //("0.0") Hold +alt1 to autothrow smokes
ConVar autosmoke_yaw_delta = null; //(0.0) Hold +alt1 to autothrow smokes
ConVar bot_placement = null; //("") Place bots at these exact positions, on map/round start or cvar change. TODO: If set, report when a bot gets flashed
ConVar disable_warmup_arenas = null; //(0) If 1, will disable the 1v1 warmup scripts
ConVar allow_weapon_cycling = null; //(0) Controls the '!weap' cheat command
ConVar smoke_success_wins_round = null; //(0) Any smoke that lands in a recognized area immediately wins the round for the thrower
ConVar default_weapons[4];
ConVar ammo_grenade_limit_total, mp_guardian_special_weapon_needed, mp_guardian_special_kills_needed;
ConVar weapon_recoil_scale, mp_damage_vampiric_amount;
ConVar mp_damage_scale_ct_head, mp_damage_scale_t_head, mp_damage_scale_ct_body, mp_damage_scale_t_body;
#include "convars_drzed"
//Write something to the server console and also the live-stream display (if applicable)
//tail -f steamcmd_linux/csgo/csgo/server_chat.log
public void PrintToStream(const char[] fmt, any ...)
{
char buffer[4096];
VFormat(buffer, sizeof(buffer), fmt, 2);
PrintToServer(buffer);
File fp = OpenFile("server_chat.log", "a");
WriteFileLine(fp, buffer);
CloseHandle(fp);
}
StringMap weapon_names;
StringMap weapon_is_primary;
StringMap weapondata_index; //weapondata_item_name[index] mapped to index
//StringMap entity_sizes_seen;
#include "cs_weapons.inc"
Handle switch_weapon_call = null;
//For anything that needs default health, we'll use this. Any time a character spawns,
//we update the default health. To my knowledge, as of 20190117, the only change to a
//player's max health is done by the game mode (Danger Zone has a default health of
//120), and is applied to every player, so it's not going to break things to have a
//single global default (which will be updated on map change once someone spawns).
int default_health = 100;
//Note that the mark is global; one player can mark and another can check pos.
float marked_pos[3];
float marked_pos2[3];
float marked_angle[3];
float marked_angle2[3];
int show_positions[MAXPLAYERS + 1];
int nshowpos = 0;
int last_freeze = -1;
int last_money[MAXPLAYERS + 1];
//Crippling is done by reducing your character's max speed. Uncrippling means getting
//you back to "normal" speed. In most situations, it won't matter exactly what this
//speed is, as long as it's no less than your weapon's speed; as of 20190121, the top
//speed available is 260 from having nothing equipped (or Bare Fists in Danger Zone).
//(The highest speed in normally-configured classic modes is 250 with the knife/C4.)
//TODO: Ascertain the actual default speed instead of assuming
#define BASE_SPEED 260.0
public void OnPluginStart()
{
RegAdminCmd("zed_money", give_all_money, ADMFLAG_SLAY);
RegAdminCmd("chat", admin_chat, ADMFLAG_SLAY);
HookEvent("player_say", Event_PlayerChat);
HookEvent("weapon_fire", Event_weapon_fire);
HookEvent("round_start", round_started);
HookEvent("round_end", uncripple_all);
HookEvent("bomb_planted", record_planter);
HookEvent("flashbang_detonate", flash_popped);
HookEvent("smokegrenade_detonate", smoke_popped);
HookEvent("grenade_bounce", smoke_bounce);
HookEvent("player_team", player_team);
HookEvent("weapon_reload", weapon_reload);
HookEvent("player_jump", player_jump);
HookEvent("bomb_begindefuse", puzzle_defuse);
HookEvent("bomb_defused", show_defuse_time);
HookEvent("player_use", player_use);
HookEvent("player_death", player_death);
//HookEvent("player_hurt", player_hurt);
//HookEvent("cs_intermission", reset_stats); //Seems to fire at the end of a match??
//HookEvent("announce_phase_end", reset_stats); //Seems to fire at halftime team swap
//player_falldamage: report whenever anyone falls, esp for a lot of dmg
AddCommandListener(player_pinged, "player_ping");
//As per carnage.sp, convars are created by the Python script.
CreateConVars();
HookConVarChange(bot_placement, update_bot_placements);
//entity_sizes_seen = CreateTrie();
weapon_names = CreateTrie();
//Weapons not mentioned will be shown by their class names.
//NOTE: Weapons that have alternates (P2000/USP-S, Deagle/R8) may be
//distinguished by netprop m_iItemDefinitionIndex - see describe_weapon().
//There are other qualities, including Strange/Stat-Trak, which can be
//seen in netprop m_iEntityQuality.
SetTrieString(weapon_names, "weapon_glock", "Glock");
SetTrieString(weapon_names, "weapon_hkp2000", "*P2000/USP*");
SetTrieString(weapon_names, "*P2000/USP*32", "P2000");
SetTrieString(weapon_names, "*P2000/USP*61", "USP-S");
SetTrieString(weapon_names, "weapon_p250", "*P250/CZ75a*");
SetTrieString(weapon_names, "*P250/CZ75a*36", "P250");
SetTrieString(weapon_names, "*P250/CZ75a*63", "CZ75a");
SetTrieString(weapon_names, "weapon_elite", "Dualies");
SetTrieString(weapon_names, "weapon_fiveseven", "Five-Seven");
SetTrieString(weapon_names, "weapon_tec9", "Tec-9");
SetTrieString(weapon_names, "weapon_deagle", "*Deagle/R8*");
SetTrieString(weapon_names, "*Deagle/R8*1", "Deagle");
SetTrieString(weapon_names, "*Deagle/R8*64", "R8");
//SMGs
SetTrieString(weapon_names, "weapon_mp9", "MP9");
SetTrieString(weapon_names, "weapon_mp7", "*MP5/MP7*");
SetTrieString(weapon_names, "*MP5/MP7*23", "MP5-SD");
SetTrieString(weapon_names, "*MP5/MP7*33", "MP7");
SetTrieString(weapon_names, "weapon_ump45", "UMP-45");
SetTrieString(weapon_names, "weapon_p90", "P90");
SetTrieString(weapon_names, "weapon_bizon", "PP-Bizon");
SetTrieString(weapon_names, "weapon_mac10", "MAC-10");
//Assault Rifles
SetTrieString(weapon_names, "weapon_ak47", "AK-47");
SetTrieString(weapon_names, "weapon_galilar", "Galil");
SetTrieString(weapon_names, "weapon_famas", "FAMAS");
SetTrieString(weapon_names, "weapon_m4a1", "*M4*");
SetTrieString(weapon_names, "*M4*16", "M4A4");
SetTrieString(weapon_names, "*M4*60", "M4A1-S");
SetTrieString(weapon_names, "weapon_aug", "AUG");
SetTrieString(weapon_names, "weapon_sg556", "SG-553");
//Snipers
SetTrieString(weapon_names, "weapon_ssg08", "Scout");
SetTrieString(weapon_names, "weapon_awp", "AWP");
SetTrieString(weapon_names, "weapon_scar20", "SCAR-20");
SetTrieString(weapon_names, "weapon_g3sg1", "G3SG1");
//Shotties
SetTrieString(weapon_names, "weapon_nova", "Nova");
SetTrieString(weapon_names, "weapon_xm1014", "XM1014");
SetTrieString(weapon_names, "weapon_mag7", "MAG-7");
SetTrieString(weapon_names, "weapon_sawedoff", "Sawed-Off");
//Grenades
SetTrieString(weapon_names, "weapon_hegrenade", "HE");
SetTrieString(weapon_names, "hegrenade_projectile", "HE"); //The thrown grenade is a separate entity class from the wielded grenade
SetTrieString(weapon_names, "weapon_molotov", "Molly"); //T-side
SetTrieString(weapon_names, "weapon_incgrenade", "Molly"); //CT-side
SetTrieString(weapon_names, "molotov_projectile", "Molly"); //Getting beaned with EITHER fire 'nade
SetTrieString(weapon_names, "inferno", "Molly"); //The actual flames
SetTrieString(weapon_names, "weapon_flashbang", "Flash"); //Non-damaging but you can still get beaned
SetTrieString(weapon_names, "flashbang_projectile", "Flash");
SetTrieString(weapon_names, "weapon_smokegrenade", "Smoke"); //Ditto
SetTrieString(weapon_names, "smokegrenade_projectile", "Smoke");
SetTrieString(weapon_names, "weapon_decoy", "Decoy"); //When wielded
SetTrieString(weapon_names, "decoy_projectile", "Decoy"); //Beaning and also the tiny boom at the end
//Other
SetTrieString(weapon_names, "weapon_m249", "M249");
SetTrieString(weapon_names, "weapon_negev", "Negev");
SetTrieString(weapon_names, "weapon_taser", "Zeus x27");
SetTrieString(weapon_names, "weapon_knife", "Knife");
SetTrieString(weapon_names, "weapon_knifegg", "Gold Knife"); //Arms Race mode only
SetTrieString(weapon_names, "weapon_c4", "C4"); //The carried C4
SetTrieString(weapon_names, "planted_c4", "C4"); //When the bomb goes off.... bladabooooom
weapon_is_primary = CreateTrie();
//Weapons not mentioned are not primary weapons. If the mapped value is 2, say "an %s".
//SMGs
SetTrieValue(weapon_is_primary, "mp9", 2);
SetTrieValue(weapon_is_primary, "mp7", 2);
SetTrieValue(weapon_is_primary, "ump45", 1);
SetTrieValue(weapon_is_primary, "p90", 1);
SetTrieValue(weapon_is_primary, "bizon", 1);
SetTrieValue(weapon_is_primary, "mac10", 1);
//Assault Rifles
SetTrieValue(weapon_is_primary, "ak47", 2);
SetTrieValue(weapon_is_primary, "galilar", 1);
SetTrieValue(weapon_is_primary, "famas", 1);
SetTrieValue(weapon_is_primary, "m4a1", 2);
SetTrieValue(weapon_is_primary, "m4a1_silencer", 2);
SetTrieValue(weapon_is_primary, "aug", 2);
SetTrieValue(weapon_is_primary, "sg556", 1);
//Snipers
SetTrieValue(weapon_is_primary, "ssg08", 2);
SetTrieValue(weapon_is_primary, "awp", 2);
SetTrieValue(weapon_is_primary, "scar20", 1);
SetTrieValue(weapon_is_primary, "g3sg1", 1);
//Shotties and LMGs
SetTrieValue(weapon_is_primary, "nova", 1);
SetTrieValue(weapon_is_primary, "xm1014", 2);
SetTrieValue(weapon_is_primary, "mag7", 1);
SetTrieValue(weapon_is_primary, "m249", 2);
SetTrieValue(weapon_is_primary, "negev", 1);
//Build a reverse lookup. Given an item name, find all its other details (price, max speed, etc).
weapondata_index = CreateTrie();
for (int i = 0; i < sizeof(weapondata_item_name); ++i) {SetTrieValue(weapondata_index, weapondata_item_name[i], i);}
//Not handled by the automated system as it's easier if we can loop over these
default_weapons[0] = FindConVar("mp_ct_default_primary");
default_weapons[1] = FindConVar("mp_t_default_primary");
default_weapons[2] = FindConVar("mp_ct_default_secondary");
default_weapons[3] = FindConVar("mp_t_default_secondary");
Handle gamedata = LoadGameConfigFile("sdkhooks.games");
StartPrepSDKCall(SDKCall_Player);
PrepSDKCall_SetFromConf(gamedata, SDKConf_Virtual, "Weapon_Switch");
PrepSDKCall_AddParameter(SDKType_CBaseEntity, SDKPass_Pointer);
PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain);
switch_weapon_call = EndPrepSDKCall();
delete gamedata;
}
public Action admin_chat(int client, int args)
{
char text[512]; GetCmdArgString(text, sizeof(text));
char admin[32]; GetConVarString(sm_drzed_admin_chat_name, admin, sizeof(admin));
ReplyToCommand(client, "%s: %s", admin, text);
PrintToChatAll(" \x04%s : \x01%s", admin, text); //Chat is in green, distinct from both team colours
return Plugin_Handled;
}
//Not quite perfectly uniform. If there's a better way, it can be changed here.
int randrange(int max) {return RoundToFloor(GetURandomFloat() * max);}
int nonrandom_numbers[] = {
4, //Number of puzzles minus one
2, //"How many total Shotguns do I have here?" -- just count 'em (7)
0, 1,
3, //"Find my largest magazine fully Automatic gun. How many shots till I reload?" -- it's a Galil (35)
0, 8, 1,
2, //"How many distinct Pistols do I have here?" -- count unique items (5)
1, 0,
0, //"This is my SMG. There are none quite like it. How well does it penetrate armor?" -- it's an MP9 (60)
2, 2,
0, //"This is my Shotgun. There are none quite like it. How many shots till I reload?" -- it's a Nova (8)
1, 0,
};
int next_nonrandom = -1;
int randctrl(int max)
{
if (next_nonrandom != -1)
{
int ret = nonrandom_numbers[next_nonrandom++];
if (next_nonrandom >= sizeof(nonrandom_numbers)) next_nonrandom = -1;
if (ret < max) return ret; //If the forced one is out of bounds, ignore it.
}
return RoundToFloor(GetURandomFloat() * max);
}
int bomb_planter = -1;
public void record_planter(Event event, const char[] name, bool dontBroadcast)
{
bomb_planter = GetClientOfUserId(event.GetInt("userid"));
}
public Action give_all_money(int initiator, int args)
{
bool nobots = false;
if (args)
{
char arg[64]; GetCmdArg(1, arg, sizeof(arg));
if (!strcmp(arg, "humans")) {nobots = true; PrintToChatAll("Giving money to all humans!");}
}
if (!nobots) PrintToChatAll("Giving money to everyone!");
for (int client = 1; client < MaxClients; ++client)
{
if (!IsClientInGame(client)) continue;
if (nobots && IsFakeClient(client)) continue;
int money = GetEntProp(client, Prop_Send, "m_iAccount") + 1000;
PrintToChat(client, "You now have $%d", money);
SetEntProp(client, Prop_Send, "m_iAccount", money);
}
}
void describe_weapon(int weapon, char[] buffer, int bufsz)
{
if (weapon == -1) {strcopy(buffer, bufsz, "(none)"); return;}
GetEntityClassname(weapon, buffer, bufsz);
GetTrieString(weapon_names, buffer, buffer, bufsz);
if (buffer[0] == '*')
{
//It's a thing with variants. Get the variant descriptor and use
//that instead/as well.
Format(buffer[strlen(buffer)], bufsz - strlen(buffer), "%d",
GetEntProp(weapon, Prop_Send, "m_iItemDefinitionIndex"));
//If the variant is listed in the trie, transform it (again)
GetTrieString(weapon_names, buffer, buffer, bufsz);
}
}
//Silence the warning "unused parameter"
any ignore(any ignoreme) {return ignoreme;}
/* Some of this would be better done by redefining the way bots buy gear; I
can't currently do this, so it's all done as chat commands.
In a real competitive match, you buy equipment *as a team*. You wait to see
what your teammates need before dropping all your money on stuff. But alas,
the bots are not such team players, and will barge ahead and make purchases
based solely on their own needs. While I can't make the bots truly act like
humans, I can at least (well, in theory, anyhow) make them a bit chattier -
make them tell you what they've already decided to do. That means the human
players don't spend valuable time panning around, trying to figure out what
the bots have done. This comes in a few varieties:
1) When a bot drops a weapon during freeze time, he will announce it unless
it is a default one (starter pistol). "BOT Opie: I'm dropping my AK-47".
2) "Someone drop me a weapon pls?" - the wealthiest bot, if any have enough
to help, drops his current primary then buys either an M4A1 or an AK-47.
3) "Bots, buy nades" - all bots attempt to buy HE, Flash, Smoke, Molotov. A
bot normally will buy only one nade per round. This is stupid. On freeze
time start, all bots will automatically buy more nades; any human on the
team can also request that bots have another shot at buying nades.
*/
public Action CS_OnCSWeaponDrop(int client, int weapon)
{
if (client > MAXPLAYERS) return;
if (!GameRules_GetProp("m_bFreezePeriod")) return; //Announce only during freeze time.
if (!IsFakeClient(client)) return; //Don't force actual players to speak - it violates expectations.
//Delay the actual message to allow a replacement weapon to be collected
Handle params;
CreateDataTimer(0.01, announce_weapon_drop, params, TIMER_FLAG_NO_MAPCHANGE);
WritePackCell(params, client);
WritePackCell(params, weapon);
//Detect the slot that this weapon goes in by looking for it pre-drop.
//This hook is executed prior to the drop actually happening, so the weapon should
//still be in the character's inventory somewhere.
for (int slot = 0; slot < 10; ++slot)
if (GetPlayerWeaponSlot(client, slot) == weapon)
WritePackCell(params, slot);
WritePackCell(params, -1); //Should really only do this if the previous line never hit, but whatevs. An extra pack integer in the weird case.
ResetPack(params);
}
Action announce_weapon_drop(Handle timer, Handle params)
{
ignore(timer);
int client = ReadPackCell(params);
int weapon = ReadPackCell(params);
int slot = ReadPackCell(params);
if (!IsClientInGame(client) || !IsValidEntity(weapon)) return; //Map changed, player left, or something like that
int newweap = GetPlayerWeaponSlot(client, slot); //Whatcha got now?
if (newweap == weapon) return; //Dropped a weapon and instantly picked it up again (seems to happen in Short Demolition mode a lot)
char player[64]; GetClientName(client, player, sizeof(player));
char cls[64]; GetEntityClassname(weapon, cls, sizeof(cls));
if (slot != 0 && slot != 1) return; //Ignore if not primary/secondary
for (int i = 0; i < sizeof(default_weapons); ++i)
{
char ignoreme[64]; GetConVarString(default_weapons[i], ignoreme, sizeof(ignoreme));
if (ignoreme[0] && !strcmp(cls, ignoreme)) return; //It's a default weapon.
}
describe_weapon(weapon, cls, sizeof(cls));
char newcls[64] = "(nothing)";
char command[256];
if (newweap != -1)
{
//Normal case: the weapon was dropped because another was bought.
describe_weapon(newweap, newcls, sizeof(newcls));
Format(command, sizeof(command), "say_team I'm dropping my %s in favour of this %s", cls, newcls);
}
else Format(command, sizeof(command), "say_team I'm dropping my %s", cls); //Theoretically they might not get a new weapon.
FakeClientCommandEx(client, command);
File fp = OpenFile("bot_weapon_drops.log", "a");
char time[64]; FormatTime(time, sizeof(time), "%Y-%m-%d %H:%M:%S", GetTime());
WriteFileLine(fp, "[%s] BOT %s dropped %s for %s", time, player, cls, newcls);
CloseHandle(fp);
}
int anarchy[66];
int anarchy_available[66];
Action add_anarchy(Handle timer, any client)
{
ignore(timer);
//Check if the player (or maybe the weapon) has drawn blood.
//~ PrintToStream("Potentially adding anarchy: av %d", anarchy_available[client]);
if (!anarchy_available[client]) return;
anarchy_available[client] = 0; anarchy[client]++;
char player[64]; GetClientName(client, player, sizeof(player));
PrintCenterText(client, "You now have %d anarchy!", anarchy[client]);
}
//Position and orient a bot (technically any client) based on 3-6 floats separated by commas
void place_bot(int bot, const char[] position)
{
char posstr[7][20];
int numpos = ExplodeString(position, ",", posstr, sizeof(posstr), sizeof(posstr[]));
if (numpos < 3) return; //Broken, ignore
float pos[3], ang[3] = {0.0, 0.0, 0.0};
int n = 0;
if (numpos >= 4 && StrEqual(posstr[n], "T" )) {ChangeClientTeam(bot, 2); n++;}
else if (numpos >= 4 && StrEqual(posstr[n], "CT")) {ChangeClientTeam(bot, 3); n++;}
else ChangeClientTeam(bot, 2); //Default to T side bots if it's not specified. I think things bug out if there's no team set.
for (int i = 0; i < 3; ++i) pos[i] = StringToFloat(posstr[n++]);
for (int i = 0; i < 3 && n < numpos; ++i) ang[i] = StringToFloat(posstr[n++]);
float not_moving[3] = {0.0, 0.0, 0.0};
//PrintToStream("Moving bot %d to location %.1f,%.1f,%.1f / %.1f,%.1f,%.1f", bot,
// pos[0], pos[1], pos[2], ang[0], ang[1], ang[2]);
TeleportEntity(bot, pos, ang, not_moving);
}
public void update_bot_placements(ConVar cvar, const char[] previous, const char[] locations)
{
if (!strlen(locations)) return;
//PrintToStream("update_bot_placements from '%s' to '%s'", previous, locations);
//Is there any sscanf-like function in SourcePawn?
//Absent such, we fracture the string, then fracture each part, then parse.
//In Pike, this would be (array(array(float)))((locations/" ")[*]/",")
char spots[MAXPLAYERS + 1][128];
int numbots = ExplodeString(locations, " ", spots, sizeof(spots), sizeof(spots[]));
while (numbots && !strlen(spots[numbots - 1])) --numbots; //Trim off any empties at the end
int p = 0;
for (int bot = 1; bot < MAXPLAYERS; ++bot)
if (IsClientInGame(bot) && IsPlayerAlive(bot) && IsFakeClient(bot))
{
//Update bot position. If we've run out of numbots, kick the bot,
//otherwise set its position to the next one.
if (p >= numbots)
{
//PrintToStream("Kicking bot %d, excess to requirements", bot);
KickClient(bot, "You have been made redundant");
continue;
}
place_bot(bot, spots[p++]);
}
while (p < numbots)
{
//PrintToStream("Adding a bot %d", p);
char name[64]; Format(name, sizeof(name), "Target %d", p + 1);
int bot = CreateFakeClient(name);
SetEntityFlags(bot, GetEntityFlags(bot) | FL_ATCONTROLS);
damage_lag_immunify(bot, 1.0);
place_bot(bot, spots[p++]);
}
}
#include "flashbang.inc"
public void flash_popped(Event event, const char[] name, bool dontBroadcast)
{
if (!GetConVarInt(learn_smoke)) return;
float pos[3];
pos[0] = event.GetFloat("x"); pos[1] = event.GetFloat("y"); pos[2] = event.GetFloat("z");
int client = GetClientOfUserId(event.GetInt("userid"));
int entity = event.GetInt("entityid");
//For analysis purposes, calculate the distance (not squared) to each
//player, and print to that player's chat how far away the flash popped.
for (int cl = 1; cl < MAXPLAYERS; ++cl) {
if (!IsClientInGame(cl) || !IsPlayerAlive(cl) || IsFakeClient(cl)) continue;
float player[3]; GetClientEyePosition(cl, player);
float dist = GetVectorDistance(pos, player, false);
PrintToChat(cl, "That flash popped at (%.2f, %.2f, %.2f) - %.2f HU away",
pos[0], pos[1], pos[2], dist);
}
//Distance calculations based in part on the figures from
//3kliksphilip's analysis at https://youtu.be/aTR7Surb80w?t=238
//with additional analysis and subjective estimates of "yeah that
//would disrupt my aim". The definition of partial flashing is NOT
//the same as the technical lack of full flash (ie max-alpha).
int targetidx = 0;
for (int i = 0; i < sizeof(flash_region_targets); ++i) {
int flashed = 0, half = 0;
for (int j = 0; j < flash_region_targets[i]; ++j) {
TR_TraceRayFilter(pos, flash_targets[targetidx], MASK_OPAQUE, RayType_EndPoint, filter_notself, entity);
/*float hit[3]; TR_GetEndPosition(hit);
PrintToChatAll("Tracing ray: %s at %.2f,%.2f,%.2f",
TR_DidHit() ? "hit": "missed", hit[0], hit[1], hit[2]);*/
if (TR_DidHit()) {++targetidx; continue;}
//It would have been visible. Cool. Calculate distance (independently).
float distsq = GetVectorDistance(pos, flash_targets[targetidx++], true);
//PrintToChatAll("D2: %.2f", distsq);
if (distsq > 1800.0*1800.0) continue;
if (distsq > 1250.0*1250.0) ++half;
else ++flashed;
}
if (flashed || half) {
char hits[20];
if (flashed && half) Format(hits, sizeof(hits), "%d(%d)", flashed, flashed + half);
else if (half) Format(hits, sizeof(hits), "(%d)", half);
else Format(hits, sizeof(hits), "%d", flashed);
PrintToChat(client, "It caught %s - %s/%d",
flash_target_regions[i], hits, flash_region_targets[i]);
}
}
}
public void SmokeLog(const char[] fmt, any ...)
{
char buffer[4096];
VFormat(buffer, sizeof(buffer), fmt, 2);
File fp = OpenFile("learn_smoke.log", "a");
WriteFileLine(fp, buffer);
CloseHandle(fp);
}
#define SMOKE_TARGETS 7
float smoke_targets[SMOKE_TARGETS][2][3] = { //Unfortunately the size has to be specified :(
//Dust II
//- Xbox
{{-400.0, 1350.0, -27.0}, {-257.0, 1475.0, -24.0}},
//- Long A Corner
{{1186.0, 1082.0, -4.0}, {1304.0, 1260.0, 3.0}},
//- B site Window
{{-1437.0, 2591.0, 108.0}, {-1250.0, 2723.0, 130.0}},
//- CT spawn (the Mid side, good for pushing into B site)
{{-251.0, 2090.0, -126.0}, {-115.0, 2175.0, -122.0}},
//- A site - protects a Long push from Goose, Site, and nearby areas
{{1064.0, 2300.0, 97.0}, {1284.0, 2625.0, 131.0}},
//- Bedroom Doors - defensive smoke to slow down a push
//(They'll usually Z btwn 2.0 and 3.5, but there's a little ledge at 7.85 that counts too)
{{590.0, 640.0, 2.0}, {764.0, 844.0, 7.86}},
//- A site Crossing smoke
{{1024.0, 2125.0, -5.0}, {1277.0, 2180.0, 5.0}}
//Add others as needed - {{x1,y1,z1},{x2,y2,z2}} where the
//second coords are all greater than the firsts.
};
char smoke_target_desc[][] = {
"Xbox smoke! ", "Corner smoke! ", "Window smoke! ", "CT spawn! ", "A site! ",
"Bedroom doors! ", "Crossing smoke! ",
};
/*
* Blue box: From the passageway from backyard into tuns, standing throw between the rafters (middle of opening).
- Precise position depends how far left/right you stand; optimal is about 75% right.
* Site: From the same passageway, hug the left wall (somewhere near the tuft of grass), and throw through the same hole.
* Car: Same passageway, hug left wall, come all the way to the arch (but not the freestanding pillar). Same hole, aim parallel to the top corrugated iron.
* Alt site: From the ambush nook just in tuns proper, aim into the biggest opening, on the right edge of it.
* Doors: Jump past the AWPer at blue box, get all the way to the corner. Aim into the rectangular gap, in the middle of the long side (left).
- Alternatively, hug the edge of the ambush nook, aim parallel to main corrugated iron, standing throw.
* Window: No standing throw found. Various moving and jumping throws possible.
- Get past the AWPer and into the corner. Hug the far wall (not the rear wall towards T spawn). Aim onto the dark spot above the door.
Then hold a crouch; your crosshair should be just above the corner of the door crease. Jump throw.
Eye angles -25.54,67.50 will work. It's fairly precise and hard to describe, takes practice.
* Flagstones: Stand btwn white angled box and pillar, aim into opening below stonework (left of ctr for safety)
- This partly smokes off Window
* Alternate car: Stand ON the white box, up against the pillar, on top of the wood slat. Aim through the hole, about two thirds down, a tad to the right.
* CT before pushing Truck:
- Jump to under Xbox. An AWPer might take a shot at you but would have to be REALLY fast. Clear Mid-to-Short.
- Push into the back corner, then step out a bit so you can see the wall behind Mid-to-Short.
- Left-click throw at the left side of the arch on the far wall. Elevation approx level with dirty patch. Fairly forgiving.
* Bedroom Doors from Short - slow down a Long push:
- Ensure that you won't get jumped. Helps to have a good Mid player who'll call a Short push.
- Push backwards into the corner above the stairs - 273,1890,163
- Crosshair on the corner of the building with the streetlight on it - -26,-67
- Standing jump throw.
* B Window to block a mid push:
- From Tuns, jump past the blue box AWPer, get to the pillar (mid side, not far side)
- Aim at the angled shadow just where it meets the shadow of the beam (above the archway)
- Standing jump throw.
*/
#define SMOKE_BOUNCE_TARGETS 1
float smoke_first_bounce[SMOKE_BOUNCE_TARGETS][2][3] = {
//1: Dust II Xbox
//NOTE: If the bounce is extremely close to the wall (-265 to -257), the
//smoke will bounce off the wall and miss. The actual boundary is somewhere
//between -260 and -265.
//The boundary isn't perfectly rectangular. There are a few points near the
//edges of this region which are promising but don't work, or not promising
//and do, and I haven't figured out exactly what the region should be. It is
//advisory and not completely accurate.
{{-322.0, 1135.0, -120.0}, {-265.0, 1275.0, -80.0}},
};
float smoke_launch_time[4096]; //Map entity ID to the game time it was released
public void smoke_popped(Event event, const char[] name, bool dontBroadcast)
{
if (!GetConVarInt(learn_smoke)) return;
float x = event.GetFloat("x"), y = event.GetFloat("y"), z = event.GetFloat("z");
int client = GetClientOfUserId(event.GetInt("userid"));
int target = -1;
//TODO: Look at the map name and pick a block of target boxes. Or maybe
//not - what are the chances that two maps will have important smoke
//targets that overlap?
for (int i = 0; i < SMOKE_TARGETS; ++i)
{
//Is there an easier way to ask if a point is inside a cube?
if (smoke_targets[i][0][0] < x && x < smoke_targets[i][1][0] &&
smoke_targets[i][0][1] < y && y < smoke_targets[i][1][1] &&
smoke_targets[i][0][2] < z && z < smoke_targets[i][1][2])
target = i;
}
//Mostly, we'll do smoke tests in infinite warmup. If we're doing them in actual rounds, it's
//probably to test timings, so show the bloom time.
char roundtime[64] = "";
if (!GameRules_GetProp("m_bWarmupPeriod"))
Format(roundtime, sizeof(roundtime), " - at %.2f sec", GetGameTime() - GameRules_GetPropFloat("m_fRoundStartTime"));
float flighttime = GetGameTime() - smoke_launch_time[event.GetInt("entityid")];
PrintToChat(client, "%sSmoke popped (%.2f, %.2f, %.2f)%s, flight %.2fs",
target >= 0 ? smoke_target_desc[target] : "",
x, y, z, roundtime, flighttime);
SmokeLog("[%d-E-%d] Pop (%.2f, %.2f, %.2f) - %s%s - %.2fs", client,
event.GetInt("entityid"),
x, y, z, target >= 0 ? "GOOD" : "FAIL", roundtime, flighttime);
if (target >= 0 && GetConVarInt(smoke_success_wins_round) && !GameRules_GetProp("m_bWarmupPeriod")) {
int winner = GetClientTeam(client);
for (int p = 1; p < MAXPLAYERS; ++p)
if (IsClientInGame(p) && IsPlayerAlive(p) && IsFakeClient(p) && GetClientTeam(p) != winner)
SDKHooks_TakeDamage(p, p, p, 1000.0);
}
}
/*
To learn to aim:
1) Record eye positions for weapon_fire if smokegrenade
2) If on_target, print eye positions
3) Can probably dial in a "rectangle" of valid eye positions that have the potential to be on_target
Is it possible to trace a ray through every eye position that succeeds and put a dot on the screen??
Maybe mark that in response to player_ping.
*/
bool smoke_not_bounced[4096];
public void smoke_bounce(Event event, const char[] name, bool dontBroadcast)
{
if (!GetConVarInt(learn_smoke)) return;
int client = GetClientOfUserId(event.GetInt("userid"));
float x = event.GetFloat("x"), y = event.GetFloat("y"), z = event.GetFloat("z"); //Undocumented event parameters!
//So, this is where things get REALLY stupid
//I want to know if this is the *first* bounce. Unfortunately, there's no
//entity ID in the event. So... we search the entire server for any smoke
//grenade projectiles. (I've no idea how to reliably expand this to other
//grenade types.) If we find that there's an entity at the exact same pos
//as the bounce sound just emanated from, then we have found it. Then, we
//look that up in a table of known grenades, and if we haven't reported a
//bounce for it yet, we flag it and report it. (TODO on that last bit.)
int ent = -1;
while ((ent = FindEntityByClassname(ent, "smokegrenade_projectile")) != -1)
{
float pos[3]; GetEntPropVector(ent, Prop_Send, "m_vecOrigin", pos);
if (pos[0] == x && pos[1] == y && pos[2] == z)
{
if (smoke_not_bounced[ent])
{
smoke_not_bounced[ent] = false;
bool on_target = false;
for (int i = 0; i < SMOKE_BOUNCE_TARGETS; ++i)
if (smoke_first_bounce[i][0][0] < x && x < smoke_first_bounce[i][1][0] &&
smoke_first_bounce[i][0][1] < y && y < smoke_first_bounce[i][1][1] &&
smoke_first_bounce[i][0][2] < z && z < smoke_first_bounce[i][1][2])
on_target = true;
PrintToChat(client, "%sgrenade_bounce: (%.2f, %.2f, %.2f), flight %.2fs",
on_target ? "Promising! " : "",
x, y, z, GetGameTime() - smoke_launch_time[ent]);
SmokeLog("[%d-D-%d] Bounce (%.2f, %.2f, %.2f) - %s", client, ent,
x, y, z, on_target ? "PROMISING" : "MISSED");
}
break;
}
}
}
int assign_flame_owner = -1;
bool report_new_entities = false;
//int nextpitch = 2530, nextyaw = 6740; //Note that pitch is the magnitude of pitch, but we actually negate it for execution.
public void OnEntityCreated(int entity, const char[] cls)
{
if (GetConVarInt(learn_smoke) && !strcmp(cls, "smokegrenade_projectile"))
{
//It's a newly-thrown smoke grenade. Mark it so we'll report its
//first bounce (if we're reporting grenade bounces).
smoke_not_bounced[entity] = true;
smoke_launch_time[entity] = GetGameTime();
CreateTimer(0.01, report_entity, entity, TIMER_FLAG_NO_MAPCHANGE);
/*
//These numbers are good for testing B Window; bind a key to "exec next_throw" and
//alternate that with a jump-throw key. To test Xbox, change or remove the setpos,
//remove the duck, and probably widen the possible pitch/yaw values quite a bit.
if (++nextyaw > 6760) {nextyaw = 6740; ++nextpitch;}
File fp = OpenFile("next_throw.cfg", "w");
WriteFileLine(fp, "//Created by drzed.sp for smoke aim drilling");
WriteFileLine(fp, "setpos_exact -2185.968750 1059.031250 39.801247");
WriteFileLine(fp, "setang -%d.%02d %d.%02d 0.0", nextpitch / 100, nextpitch % 100, nextyaw / 100, nextyaw % 100);
WriteFileLine(fp, "+duck");
WriteFileLine(fp, "+attack");
CloseHandle(fp);
// */
}
//else if (entity_sizes_seen) CreateTimer(0.01, report_entity, entity, TIMER_FLAG_NO_MAPCHANGE);
if (!strcmp(cls, "entityflame")) SetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity", assign_flame_owner);
if (report_new_entities)
PrintToChatAll("New: %s [%d]", cls, entity);
}
Action report_entity(Handle timer, any entity)
{
ignore(timer);
if (!IsValidEntity(entity)) return;
char cls[64]; GetEdictClassname(entity, cls, sizeof(cls));
/*
int seen;
if (!GetTrieValue(entity_sizes_seen, cls, seen)) {
SetTrieValue(entity_sizes_seen, cls, 1);
float mins[3]; GetEntPropVector(entity, Prop_Data, "m_vecMins", mins);
float maxs[3]; GetEntPropVector(entity, Prop_Data, "m_vecMaxs", maxs);
PrintToStream("%s: (%.3f,%.3f,%.3f)-(%.3f,%.3f,%.3f)", cls,
mins[0], mins[1], mins[2],
maxs[0], maxs[1], maxs[2]);
}
*/
if (!strcmp(cls, "smokegrenade_projectile"))
{
int client = GetEntPropEnt(entity, Prop_Send, "m_hThrower");
if (client != -1) SmokeLog("[%d-C-%d] Spawn", client, entity);
}
else if (!strcmp(cls, "info_player_ping"))
{
PrintToStream("New ping: %d", entity);
int target = GetEntPropEnt(entity, Prop_Send, "m_hPingedEntity");
if (target > 0) {
PrintToStream("Pinged %d Player %d Type %d",
target,
GetEntPropEnt(entity, Prop_Send, "m_hPlayer"),
GetEntProp(entity, Prop_Send, "m_iType")
);
PrintToStream("Ent %d render FX %d mode %d",
target,
GetEntProp(target, Prop_Send, "m_nRenderFX"),
GetEntProp(target, Prop_Send, "m_nRenderMode")
);
}
int pingclient = GetEntPropEnt(entity, Prop_Send, "m_hPlayer");
int playerping = pingclient == -1 ? -1 : GetEntPropEnt(pingclient, Prop_Send, "m_hPlayerPing");
int team = pingclient == -1 ? -1 : GetClientTeam(pingclient);
int pingteam = GetEntProp(entity, Prop_Send, "m_iTeamNum");
int pingtype = GetEntProp(entity, Prop_Send, "m_iType");
PrintToStream("Client %d (team %d), ping %d, team %d, type %d",
pingclient, team, playerping, pingteam, pingtype);
}
}
//Not really public, but not always used, so suppress the warning
public Action unreport_new(Handle timer, any entity)
{
ignore(timer);
report_new_entities = false;
}
//Tick number when you last jumped or last threw a smoke grenade
int last_jump[64];
int last_smoke[64];
public void player_jump(Event event, const char[] name, bool dontBroadcast)
{
if (!GetConVarInt(learn_smoke)) return;
int client = GetClientOfUserId(event.GetInt("userid"));
//Record timestamp for the sake of a jump-throw. If you then throw a smoke,
//or if you just recently did, report it.
int now = GetGameTickCount();
if (now < last_smoke[client] + 32 && now >= last_smoke[client])
{
if (now == last_smoke[client])
PrintToChat(client, "You smoked and jumped simultaneously (-0)");
else
PrintToChat(client, "You smoked -%d before jumping", now - last_smoke[client]);
SmokeLog("[%d-B] JumpThrow -%d", client, now - last_smoke[client]);
}
last_jump[client] = now;
}
#include "underdome.inc"
int underdome_mode = 0, underdome_flg = 0;
int killsneeded;
float last_guardian_buy_time = 0.0;
Handle underdome_ticker = INVALID_HANDLE;
int spray_count[MAXPLAYERS + 1]; //Number of bullets fired since the last time all attack buttons were released
void keep_firing(int client)
{
//Increase fire rate based on the length of time you've been firing
int weap = GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon");
if (weap <= 0) return;
float clip = 1.0;
char cls[64]; GetEntityClassname(weap, cls, sizeof(cls));
int idx;
if (GetTrieValue(weapondata_index, cls, idx)) clip = weapondata_primary_clip_size[idx];
if (clip < 1.0) clip = 1.0; //Shouldn't happen
float delay = GetEntPropFloat(weap, Prop_Send, "m_flNextPrimaryAttack") - GetGameTime();
//PrintCenterTextAll("Delay: %.3f", delay);
float scale = 1.0 - 0.5 * spray_count[client] / clip;
if (scale < 0.25) scale = 0.25; //Max out at four-to-one fire rate boosting (otherwise scale could even go negative in theory)
SetEntPropFloat(weap, Prop_Send, "m_flNextPrimaryAttack", GetGameTime() + delay * scale);
//Random chance to consume no ammo, based on spray length
//If clip is full or empty, always consume ammo (to prevent weirdnesses)
//Cap the chance at 75%, which will mean that you should eventually run out
int clipleft = GetEntProp(weap, Prop_Send, "m_iClip1");
int chance = spray_count[client];
if (clipleft == RoundToFloor(clip) || clipleft == 0) chance = 0;
else if (chance > 75) chance = 75;
if (randrange(100) < chance)
SetEntProp(weap, Prop_Send, "m_iClip1", clipleft + 1);
}
void slow_firing(int client)
{
int rate = GetConVarInt(limit_fire_rate); //Restrict fire rate to X rounds/min
int weap = GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon");
if (weap <= 0 || rate <= 0) return;
float delay = GetEntPropFloat(weap, Prop_Send, "m_flNextPrimaryAttack") - GetGameTime();
if (rate == 1) //Show, rather than changing, the limit
{
PrintCenterText(client, "Calculated fire rate: %.2f", 60.0 / delay);
return;
}
float min_delay = 60.0 / rate; //Seconds between shots (usually a small fraction of one)
if (delay < min_delay)
SetEntPropFloat(weap, Prop_Send, "m_flNextPrimaryAttack", GetGameTime() + min_delay);
}
int strafe_direction[MAXPLAYERS + 1]; //1 = right, 0 = neither/both, -1 = left. This is your *goal*, not your velocity or acceleration.
int stutterstep_score[MAXPLAYERS + 1][3]; //For each player, ({stationary, accurate, inaccurate}), and is reset on weapon reload
float stutterstep_inaccuracy[MAXPLAYERS + 1]; //For each player, the sum of squares of the inaccuracies, for the third field above.
float current_weapon_speed[MAXPLAYERS + 1]; //For each player, the max speed of the weapon that was last equipped. An optimization.
float stutterstep_lastpos[MAXPLAYERS + 1][3]; //F e p, position when last shot was taken.
int stutterstep_lastshottime[MAXPLAYERS + 1]; //F e p, game tick count when last shot was taken.
float stutterstep_width[MAXPLAYERS + 1]; //F e p, running tally of your "width score", the exact definition of which may change
int stutterstep_widthcnt[MAXPLAYERS + 1]; //F e p, number of entries in the width score (for averaging)
void show_stutterstep_stats(int client)
{
char player[64]; GetClientName(client, player, sizeof(player));
int shots = stutterstep_score[client][1] + stutterstep_score[client][2];
if (stutterstep_score[client][0] + shots == 0) return; //No stats to show (can happen if you reload two weapons in succession)
PrintToChatAll("%s: stopped %d, accurate %d, inaccurate %d - spread %.2f, width %.2f", player,
stutterstep_score[client][0], stutterstep_score[client][1], stutterstep_score[client][2],
shots ? stutterstep_inaccuracy[client] / shots : 0.0,
stutterstep_widthcnt[client] ? stutterstep_width[client] / stutterstep_widthcnt[client] : 0.0);
stutterstep_score[client][0] = stutterstep_score[client][1] = stutterstep_score[client][2] = 0;
stutterstep_inaccuracy[client] = stutterstep_width[client] = 0.0;
stutterstep_lastshottime[client] = stutterstep_widthcnt[client] = 0;
}
void spawn_ping(int client, float pos[3])
{
PrintToChatAll("Pinging at (%.2f,%.2f,%.2f)", pos[0], pos[1], pos[2]);
int prevping = GetEntPropEnt(client, Prop_Send, "m_hPlayerPing");
if (prevping > 0) AcceptEntityInput(prevping, "Kill");
int ping = CreateEntityByName("info_player_ping");
DispatchSpawn(ping);
SetEntPropEnt(ping, Prop_Send, "m_hPlayer", client);
SetEntPropEnt(client, Prop_Send, "m_hPlayerPing", ping);
SetEntProp(ping, Prop_Send, "m_iTeamNum", GetClientTeam(client));
//SetEntProp(ping, Prop_Send, "m_iType", 0); //Type 18 is an "urgent" ping
TeleportEntity(ping, pos, NULL_VECTOR, NULL_VECTOR);
}
public void Event_weapon_fire(Event event, const char[] name, bool dontBroadcast)
{
int client = GetClientOfUserId(event.GetInt("userid"));
char weapon[64]; event.GetString("weapon", weapon, sizeof(weapon));
if (GetConVarInt(learn_smoke) && !strcmp(weapon, "weapon_awp"))
{
float pos[3];
pos[0] = 0.0; pos[1] = 0.0; pos[2] = 0.0;
int team = GetClientTeam(client);
for (int p = 1; p < MAXPLAYERS; ++p)
if (IsClientInGame(p) && IsPlayerAlive(p) && GetClientTeam(p) != team)
GetClientEyePosition(p, pos);
spawn_ping(client, pos);
}
if (GetConVarInt(learn_smoke) && !strcmp(weapon, "weapon_smokegrenade"))
{
//If you just fired a smoke, record timestamp for the sake of a jump-throw.
int now = GetGameTickCount();
float pos[3]; GetClientEyePosition(client, pos);
float angle[3]; GetClientEyeAngles(client, angle);
char roundtime[64] = "";
if (!GameRules_GetProp("m_bWarmupPeriod"))
Format(roundtime, sizeof(roundtime), " - at %.2f sec", GetGameTime() - GameRules_GetPropFloat("m_fRoundStartTime"));
PrintToChat(client, "Smoked looking (%.2f, %.2f)%s", angle[0], angle[1], roundtime);
SmokeLog("[%d-A] Smoke (%.2f, %.2f, %.2f) - (%.2f, %.2f)", client,
pos[0], pos[1], pos[2], angle[0], angle[1]);
if (now < last_jump[client] + 32 && now >= last_jump[client])
{
if (now == last_jump[client])
PrintToChat(client, "You jumped and smoked simultaneously (+0)");
else
PrintToChat(client, "You smoked +%d after jumping", now - last_jump[client]);
SmokeLog("[%d-B] JumpThrow +%d", client, now - last_jump[client]);
}
last_smoke[client] = now;
}
spray_count[client]++;
if (GetConVarInt(learn_stutterstep))
{
//Stutter stepping
//Every time a shot is fired:
// * Show the total velocity. Should match cl_showpos 1
// * Get eye angles. Calculate the proportion of velocity which is perpendicular to the horizontal eye angle.
// - What about the vertical proportion?
// * Inspect currently-held buttons. Are you increasing or decreasing velocity?
// * Summarize with a score number: 0.0 is perfect, positive means too late, negative too soon
// * Don't bother doing anything about aim synchronization - the pockmarks can teach that.
// * Show the current keys and the sideways movement as center text
float vel[3]; //Velocity seems to be three floats, NOT a vector. Why? No clue.
vel[0] = GetEntPropFloat(client, Prop_Send, "m_vecVelocity[0]");
vel[1] = GetEntPropFloat(client, Prop_Send, "m_vecVelocity[1]");
vel[2] = GetEntPropFloat(client, Prop_Send, "m_vecVelocity[2]");
float spd = GetVectorLength(vel, false); //Should be equal to what cl_showpos tells you your velocity is
float angle[3]; GetClientEyeAngles(client, angle);
float right[3]; GetAngleVectors(angle, NULL_VECTOR, right, NULL_VECTOR); //Unit vector perpendicular to the way you're facing
float right_vel = GetVectorDotProduct(vel, right); //Magnitude of the velocity projected onto the (unit) right hand vector
int sidestep = spd > 0.0 ? RoundToNearest(FloatAbs(right_vel) * 100.0 / spd) : 0; //Proportion of your total velocity that is across your screen (and presumably your enemy's)
float maxspeed = current_weapon_speed[client];
if (maxspeed == 0.0) maxspeed = 250.0; //Or use the value from sv_maxspeed?
maxspeed *= 0.34; //Below 34% of a weapon's maximum speed, you are fully accurate.
int quality = spd == 0.0 && !strafe_direction[client] ? 0 : //Stationary shot.
spd <= maxspeed ? 1 : //Accurate shot.
2; //Inaccurate shot.
stutterstep_score[client][quality]++;
if (quality == 2) stutterstep_inaccuracy[client] += Pow(spd / maxspeed, 2.0) - 1.0;
char quality_desc[][] = {"stopped", "good", "bad"};
char sync_desc[64] = "";
if (strafe_direction[client])
{
//Ideally your spd should be close to zero. However, it's the right_vel
//(which is a signed value) that can be usefully compared to your goal
//(in strafe_direction). By multiplying them, we get a signed velocity
//relative to your goal direction; a positive number means you're now
//increasing your velocity (unless at max), negative means decreasing.
right_vel *= strafe_direction[client];
int sync = RoundToFloor(spd); if (right_vel < 0) sync = -sync;
//Why can't I just display a number with %+d ??? sigh.
Format(sync_desc, sizeof(sync_desc), " sync %s%d", sync > 0 ? "+" : "", sync);
}
int tm = GetGameTickCount();
float pos[3]; GetClientEyePosition(client, pos);
char width_desc[64] = "";
if (tm - stutterstep_lastshottime[client] < 64) //Assuming 64-tick server for now
{
float dist = GetVectorDistance(pos, stutterstep_lastpos[client], false);
Format(width_desc, sizeof(width_desc), " WIDTH %.1f/%d", dist, tm - stutterstep_lastshottime[client]);
//Rather than simply averaging the distances, would it be better to
//take the distance-squared ("true" for third arg to GetVectorDistance)
//and average those, and take the sqrt at the end? That skews the average
//towards the higher numbers. Alternatively, should the min and max be
//taken into account? Bear in mind that there'll be outliers (eg if you
//short burst instead of single firing, it'll probably give you a very
//low distance moved). The current calculation assumes an intention of
//single-shot stutter stepping where you move left, fire once, move right,
//fire once more, etc. Firing more than one shot per strafe will lower
//the average width.
stutterstep_width[client] += dist; stutterstep_widthcnt[client]++;
}
stutterstep_lastshottime[client] = tm;
GetClientEyePosition(client, stutterstep_lastpos[client]); //Yes, I could assign from pos, can't be bothered
PrintToChat(client, "Stutter: speed %.2f/%.0f side %d%% %s%s%s",
spd, maxspeed, sidestep, quality_desc[quality], sync_desc, width_desc);
//If this is the last shot from the magazine, show stats, since the weapon_reload
//event doesn't fire.
int weap = GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon");
if (weap > 0 && GetEntProp(weap, Prop_Send, "m_iClip1") == 1) show_stutterstep_stats(client);
}
if (GetConVarInt(limit_fire_rate)) RequestFrame(slow_firing, client);
else if (underdome_flg & UF_SALLY) RequestFrame(keep_firing, client);
//If you empty your clip completely, add a stack of Anarchy
if (anarchy[client] < GetConVarInt(sm_drzed_max_anarchy))