forked from Syihabuddinsanni/gamesense-workshop-luas
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Scoreboard equipment.lua
1101 lines (874 loc) · 32.6 KB
/
Scoreboard equipment.lua
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
--
-- dependencies
--
local csgo_weapons = require "gamesense/csgo_weapons"
local table_clear = require "table.clear"
--
-- constants
--
local EVENT_IDX_TO_WEAPON = setmetatable({}, {
__index = function(tbl, idx)
tbl[idx] = csgo_weapons[tonumber(idx)] or false
return tbl[idx]
end
})
local ITEM_KEVLAR = csgo_weapons["item_kevlar"]
local ITEM_ASSAULTSUIT = csgo_weapons["item_assaultsuit"]
local ITEM_HEAVYASSAULTSUIT = csgo_weapons["item_heavyassaultsuit"]
local ITEM_CUTTERS = csgo_weapons["item_cutters"]
local ITEM_DEFUSER = csgo_weapons["item_defuser"]
local WEAPON_TASER = csgo_weapons["weapon_taser"]
local WEAPON_C4 = csgo_weapons["weapon_c4"]
local TEAM_T = 2
local TEAM_CT = 3
--
-- utility functions
--
local function deep_compare(tbl1, tbl2)
for key1, value1 in pairs(tbl1) do
local value2 = tbl2[key1]
if value2 == nil then
-- avoid the type call for missing keys in tbl2 by directly comparing with nil
return false
elseif value1 ~= value2 then
if type(value1) == "table" and type(value2) == "table" then
if not deep_compare(value1, value2) then
return false
end
else
return false
end
end
end
-- check for missing keys in tbl1
for key2, _ in pairs(tbl2) do
if tbl1[key2] == nil then
return false
end
end
return true
end
local function table_map_filter(tbl, callback)
local new, j = {}, 1
for i=1, #tbl do
local value = callback(tbl[i])
if value ~= nil then
new[j] = value
j = j + 1
end
end
return new
end
local function table_map_assoc(tbl, callback)
local new = {}
for key, value in pairs(tbl) do
local new_key, new_value = callback(key, value)
new[new_key] = new_value
end
return new
end
local function table_contains(tbl, val)
for i=1,#tbl do
if tbl[i] == val then
return true
end
end
return false
end
local function table_remove_item(tbl, item)
for i=#tbl, 1, -1 do
if tbl[i] == item then
table.remove(tbl, i)
end
end
end
--
-- since events are unordered, we sort them here
--
local event_sort_pos = {
item_remove = 0,
player_disconnect = 0,
player_death = 0,
player_spawn = 1,
item_pickup = 2,
item_equip = 2,
}
local function sort_events_cb(a, b)
local a_i = event_sort_pos[a[1]] or a[1]:byte()
local b_i = event_sort_pos[b[1]] or b[1]:byte()
return a_i < b_i
end
local delayed_events = {}
local event_callbacks = {}
local event_callbacks_orig = {}
local delayed_events_curtime
local function run_pending_callbacks()
if delayed_events_curtime ~= nil then
table.sort(delayed_events, sort_events_cb)
for i=1, #delayed_events do
local event, e, curtime = unpack(delayed_events[i])
local handlers = event_callbacks[event]
for j=1, #handlers do
xpcall(handlers[j], client.error_log, e)
end
end
table_clear(delayed_events)
delayed_events_curtime = nil
end
end
local function add_delayed_callback(event, callback)
if event_callbacks[event] == nil then
local handlers = {}
event_callbacks_orig[event] = function(e)
local curtime = globals.curtime()
-- if curtime changed dispatch all pending events right now
if delayed_events_curtime == nil then
delayed_events_curtime = curtime
elseif delayed_events_curtime ~= curtime then
run_pending_callbacks()
delayed_events_curtime = curtime
end
table.insert(delayed_events, {event, e})
end
client.set_event_callback(event, event_callbacks_orig[event])
event_callbacks[event] = handlers
end
table.insert(event_callbacks[event], callback)
end
local function clear_delayed_callbacks()
for event, callback_orig in pairs(event_callbacks_orig) do
client.unset_event_callback(event, callback_orig)
end
table_clear(event_callbacks_orig)
table_clear(event_callbacks)
table_clear(delayed_events)
delayed_events_curtime = nil
end
--
-- js context / code block
--
local jsc = panorama.open("CSGOHud")
local FriendsListAPI, MyPersonaAPI, GameStateAPI = jsc.FriendsListAPI, jsc.MyPersonaAPI, jsc.GameStateAPI
local js = panorama.loadstring([[
let entity_panels = {}
let entity_flair_panels = {}
let entity_data = {}
let event_callbacks = {}
let unmuted_players = {}
let TEAM_COLORS = {
CT: "#B5D4EE40",
TERRORIST: "#EAD18A61"
}
let SHADOW_COLORS = {
CT: "#393C40",
TERRORIST: "#4C4844"
}
let HIDDEN_IDS = ["id-sb-name__commendations__leader", "id-sb-name__commendations__teacher", "id-sb-name__commendations__friendly", "id-sb-name__musickit"]
let SLOT_LAYOUT = `
<root>
<Panel style="min-width: 3px; padding-top: 2px; padding-left: 2px; overflow: noclip;">
<Image id="smaller" textureheight="15" style="horizontal-align: center; opacity: 0.01; transition: opacity 0.1s ease-in-out 0.0s, img-shadow 0.12s ease-in-out 0.0s; overflow: noclip; padding: 3px 5px; margin: -3px -5px;" />
<Image id="small" textureheight="17" style="horizontal-align: center; opacity: 0.01; transition: opacity 0.1s ease-in-out 0.0s, img-shadow 0.12s ease-in-out 0.0s; overflow: noclip; padding: 3px 5px; margin: -3px -5px;" />
<Image id="medium" textureheight="18" style="horizontal-align: center; opacity: 0.01; transition: opacity 0.1s ease-in-out 0.0s, img-shadow 0.12s ease-in-out 0.0s; overflow: noclip; padding: 3px 5px; margin: -3px -5px; margin-top: -4px;" />
<Image id="large" textureheight="21" style="horizontal-align: center; opacity: 0.01; transition: opacity 0.1s ease-in-out 0.0s, img-shadow 0.12s ease-in-out 0.0s; overflow: noclip; padding: 3px 5px; margin: -3px -5px; margin-top: -5px;" />
</Panel>
</root>
`
let MIN_WIDTHS = {}
let MAX_WIDTHS = {}
let SLOT_OVERRIDE = {}
let GameStateAPI_IsLocalPlayerPlayingMatch_prev
let FriendsListAPI_IsSelectedPlayerMuted_prev
let GameStateAPI_IsSelectedPlayerMuted_prev
let my_xuid = MyPersonaAPI.GetXuid()
let _SetMinMaxWidth = function(weapon, min_width, max_width, slot_override) {
if(min_width)
MIN_WIDTHS[weapon] = min_width
if(max_width)
MAX_WIDTHS[weapon] = max_width
if(slot_override)
SLOT_OVERRIDE[weapon] = slot_override
}
let _DestroyEntityPanels = function() {
for(key in entity_panels){
let panel = entity_panels[key]
if(panel != null && panel.IsValid()) {
var parent = panel.GetParent()
HIDDEN_IDS.forEach(id => {
let panel = parent.FindChildTraverse(id)
if(panel != null) {
panel.style.maxWidth = "28px"
panel.style.margin = "0px 5px 0px 5px"
}
})
if(parent.FindChildTraverse("id-sb-skillgroup-image") != null) {
parent.FindChildTraverse("id-sb-skillgroup-image").style.margin = "0px 0px 0px 0px"
}
panel.DeleteAsync(0.0)
}
delete entity_panels[key]
}
}
let _GetOrCreateCustomPanel = function(xuid) {
if(entity_panels[xuid] == null || !entity_panels[xuid].IsValid()){
entity_panels[xuid] = null
// $.Msg("creating panel for ", xuid)
let scoreboard_context_panel = $.GetContextPanel().FindChildTraverse("ScoreboardContainer").FindChildTraverse("Scoreboard") || $.GetContextPanel().FindChildTraverse("id-eom-scoreboard-container").FindChildTraverse("Scoreboard")
if(scoreboard_context_panel == null){
// usually happens if end of match scoreboard is open. clean up everything?
_Clear()
_DestroyEntityPanels()
return
}
scoreboard_context_panel.FindChildrenWithClassTraverse("sb-row").forEach(function(el){
let scoreboard_el
if(el.m_xuid == xuid) {
el.Children().forEach(function(child_frame){
let stat = child_frame.GetAttributeString("data-stat", "")
if(stat == "name") {
scoreboard_el = child_frame.GetChild(0)
} else if(stat == "flair") {
entity_flair_panels[xuid] = child_frame.GetChild(0)
}
})
if(scoreboard_el) {
let scoreboard_el_parent = scoreboard_el.GetParent()
// fix some style. this is not restored
// scoreboard_el_parent.style.overflow = "clip clip;"
// create panel
let custom_weapons = $.CreatePanel("Panel", scoreboard_el_parent, "custom-weapons", {
style: "overflow: noclip; width: fit-children; margin: 0px 0px 0px 0px; padding: 1px 0px 0px 0px; height: 100%; flow-children: left; min-width: 30px;"
})
HIDDEN_IDS.forEach(id => {
let panel = scoreboard_el_parent.FindChildTraverse(id)
if(panel != null) {
panel.style.maxWidth = "0px"
panel.style.margin = "0px"
}
})
if(scoreboard_el_parent.FindChildTraverse("id-sb-skillgroup-image") != null) {
scoreboard_el_parent.FindChildTraverse("id-sb-skillgroup-image").style.margin = "0px 0px 0px 5px"
}
scoreboard_el_parent.MoveChildBefore(custom_weapons, scoreboard_el_parent.GetChild(1))
// create child panels
let panel_armor = $.CreatePanel("Image", custom_weapons, "armor", {
textureheight: "17",
style: "padding-left: 2px; padding-top: 3px; opacity: 0.2; padding-left: 5px;"
})
panel_armor.visible = false
let panel_helmet = $.CreatePanel("Image", custom_weapons, "helmet", {
textureheight: "22",
style: "padding-left: 2px; padding-top: 0px; opacity: 0.2; padding-left: 0px; margin-left: 3px; margin-right: -3px;"
})
panel_helmet.visible = false
panel_helmet.SetImage("file://{images}/icons/equipment/helmet.svg")
for(i=24; i >= 0; i--) {
let panel_slot_parent = $.CreatePanel("Panel", custom_weapons, `weapon-${i}`)
panel_slot_parent.visible = false
panel_slot_parent.BLoadLayoutFromString(SLOT_LAYOUT, false, false)
}
// custom_weapons.style.border = "1px solid red;"
entity_panels[xuid] = custom_weapons
return custom_weapons
}
}
})
}
return entity_panels[xuid]
}
let _UpdatePlayer = function(entindex, weapons, selected_weapon, armor) {
if(entindex == null || entindex == 0)
return
entity_data[entindex] = arguments
}
let _ApplyPlayer = function(entindex, weapons, selected_weapon, armor) {
let xuid = GameStateAPI.GetPlayerXuidStringFromEntIndex(entindex)
// $.Msg("applying for ", entindex, ": ", weapons)
let panel = _GetOrCreateCustomPanel(xuid)
if(panel == null)
return
let team = GameStateAPI.GetPlayerTeamName(xuid)
let wash_color = TEAM_COLORS[team] || "#ffffffff"
// panel.style.marginRight = entity_flair_panels[entindex].actuallayoutwidth < 4 ? "-25px" : "0px"
for(i=0; i < 24; i++) {
let panel_slot_parent = panel.FindChild(`weapon-${i}`)
if(weapons && weapons[i]) {
let weapon = weapons[i]
let selected = weapon == selected_weapon
panel_slot_parent.visible = true
let slot_override = SLOT_OVERRIDE[weapon] || "small"
let panel_slot
panel_slot_parent.Children().forEach(function(el){
if(el.id == slot_override){
el.visible = true
panel_slot = el
} else {
el.visible = false
}
})
panel_slot.style.opacity = selected ? "0.85" : "0.35"
let shadow_color = SHADOW_COLORS[team] || "#58534D"
// shadow_color = "rgba(64, 64, 64, 0.1)"
panel_slot.style.imgShadow = selected ? (shadow_color + " 0px 0px 3px 3.75") : "none"
panel_slot.style.washColorFast = wash_color
panel_slot.SetImage("file://{images}/icons/equipment/" + weapon + ".svg")
// panel_slot.style.border = "1px solid red;"
panel_slot.style.marginLeft = "-5px"
panel_slot.style.marginRight = "-5px"
if(weapon == "knife_ursus") {
panel_slot.style.marginLeft = "-2px"
} else if(weapon == "knife_widowmaker") {
panel_slot.style.marginLeft = "-3px"
} else if(weapon == "hkp2000") {
panel_slot.style.marginRight = "-4px"
} else if(weapon == "incgrenade") {
panel_slot.style.marginLeft = "-6px"
} else if(weapon == "flashbang") {
panel_slot.style.marginLeft = "-5px"
}
panel_slot_parent.style.minWidth = MIN_WIDTHS[weapon] || "0px"
panel_slot_parent.style.maxWidth = MAX_WIDTHS[weapon] || "1000px"
} else if(panel_slot_parent.visible) {
// $.Msg("removed!")
panel_slot_parent.visible = false
let panel_slot = panel_slot_parent.GetChild(0)
panel_slot.style.opacity = "0.01"
}
}
let panel_armor = panel.FindChild("armor")
let panel_helmet = panel.FindChild("helmet")
if(armor != null){
panel_armor.visible = true
panel_armor.style.washColorFast = wash_color
if(armor == "helmet") {
panel_armor.SetImage("file://{images}/icons/equipment/kevlar.svg")
panel_helmet.visible = true
panel_helmet.style.washColorFast = wash_color
} else {
panel_armor.SetImage("file://{images}/icons/equipment/" + armor + ".svg")
}
} else {
panel_armor.visible = false
panel_helmet.visible = false
}
return true
}
let _ApplyData = function() {
for(entindex in entity_data) {
entindex = parseInt(entindex)
let xuid = GameStateAPI.GetPlayerXuidStringFromEntIndex(entindex)
if(!entity_data[entindex].applied || entity_panels[xuid] == null || !entity_panels[xuid].IsValid()) {
if(_ApplyPlayer.apply(null, entity_data[entindex])) {
// $.Msg("successfully appied for ", entindex)
entity_data[entindex].applied = true
}
}
}
}
let _EnablePlayingMatchHook = function() {
if(GameStateAPI_IsLocalPlayerPlayingMatch_prev == null) {
GameStateAPI_IsLocalPlayerPlayingMatch_prev = GameStateAPI.IsLocalPlayerPlayingMatch
GameStateAPI.IsLocalPlayerPlayingMatch = function() {
if(GameStateAPI.IsDemoOrHltv()) {
return true
}
return GameStateAPI_IsLocalPlayerPlayingMatch_prev.call(GameStateAPI)
}
}
}
let _DisablePlayingMatchHook = function() {
if(GameStateAPI_IsLocalPlayerPlayingMatch_prev != null) {
GameStateAPI.IsLocalPlayerPlayingMatch = GameStateAPI_IsLocalPlayerPlayingMatch_prev
GameStateAPI_IsLocalPlayerPlayingMatch_prev = null
}
}
let _EnableSelectedPlayerMutedHook = function() {
if(FriendsListAPI_IsSelectedPlayerMuted_prev == null) {
FriendsListAPI_IsSelectedPlayerMuted_prev = FriendsListAPI.IsSelectedPlayerMuted
FriendsListAPI.IsSelectedPlayerMuted = function(xuid) {
if(xuid == my_xuid) {
return false
}
return FriendsListAPI_IsSelectedPlayerMuted_prev.call(FriendsListAPI, xuid)
}
}
if(GameStateAPI_IsSelectedPlayerMuted_prev == null) {
GameStateAPI_IsSelectedPlayerMuted_prev = GameStateAPI.IsSelectedPlayerMuted
GameStateAPI.IsSelectedPlayerMuted = function(xuid) {
if(xuid == my_xuid) {
return false
}
return GameStateAPI_IsSelectedPlayerMuted_prev.call(GameStateAPI, xuid)
}
}
}
let _DisableSelectedPlayerMutedHook = function() {
if(FriendsListAPI_IsSelectedPlayerMuted_prev != null) {
FriendsListAPI.IsSelectedPlayerMuted = FriendsListAPI_IsSelectedPlayerMuted_prev
FriendsListAPI_IsSelectedPlayerMuted_prev = null
}
if(GameStateAPI_IsSelectedPlayerMuted_prev != null) {
GameStateAPI.IsSelectedPlayerMuted = GameStateAPI_IsSelectedPlayerMuted_prev
GameStateAPI_IsSelectedPlayerMuted_prev = null
}
}
let _UnmutePlayer = function(xuid) {
if(GameStateAPI.IsSelectedPlayerMuted(xuid)) {
GameStateAPI.ToggleMute(xuid)
unmuted_players[xuid] = true
return true
}
return false
}
let _RestoreUnmutedPlayers = function(xuid) {
for(xuid in unmuted_players) {
if(!GameStateAPI.IsSelectedPlayerMuted(xuid) && GameStateAPI.IsPlayerConnected(xuid)) {
GameStateAPI.ToggleMute(xuid)
}
}
unmuted_players = {}
}
let _GetAllPlayers = function() {
let result = []
for(entindex=1; entindex <= 64; entindex++) {
let xuid = GameStateAPI.GetPlayerXuidStringFromEntIndex(entindex)
if(xuid && xuid != "0") {
result.push(xuid)
}
}
return result
}
let _Create = function() {
event_callbacks["OnOpenScoreboard"] = $.RegisterForUnhandledEvent("OnOpenScoreboard", _ApplyData)
event_callbacks["Scoreboard_UpdateEverything"] = $.RegisterForUnhandledEvent("Scoreboard_UpdateEverything", function(){
// $.Msg("cleared applied data")
for(entindex in entity_data) {
// entity_data[entindex].applied = false
}
_ApplyData()
})
event_callbacks["Scoreboard_UpdateJob"] = $.RegisterForUnhandledEvent("Scoreboard_UpdateJob", _ApplyData)
}
let _Clear = function() {
entity_data = {}
}
let _Destroy = function() {
// clear entity data
_Clear()
_DestroyEntityPanels()
for(event in event_callbacks){
$.UnregisterForUnhandledEvent(event, event_callbacks[event])
delete event_callbacks[event]
}
// $.GetContextPanel().FindChildTraverse("TeamSmallContainerCT").style.width = "400px"
// $.GetContextPanel().FindChildTraverse("TeamSmallContainerT").style.width = "400px"
}
return {
create: _Create,
set_min_max_width: _SetMinMaxWidth,
destroy: _Destroy,
clear: _Clear,
update_player: _UpdatePlayer,
enable_playing_match_hook: _EnablePlayingMatchHook,
disable_playing_match_hook: _DisablePlayingMatchHook,
enable_selected_player_muted_hook: _EnableSelectedPlayerMutedHook,
disable_selected_player_muted_hook: _DisableSelectedPlayerMutedHook,
unmute_player: _UnmutePlayer,
restore_unmuted_players: _RestoreUnmutedPlayers,
get_all_players: _GetAllPlayers
}
]], "CSGOHud")()
--
-- logic for sorting weapons
--
local sort_pos = {
[csgo_weapons["weapon_hegrenade"]] = 10,
[csgo_weapons["weapon_decoy"]] = csgo_weapons["weapon_molotov"].idx-1,
[csgo_weapons["weapon_smokegrenade"]] = csgo_weapons["weapon_smokegrenade"].idx-1,
[csgo_weapons["weapon_taser"]] = 3,
}
local name_add_weapon, name_add_max = {}, 0
for idx, weapon in pairs(csgo_weapons) do
local name_add = string.byte(weapon.name)
name_add_weapon[weapon] = name_add
name_add_max = math.max(name_add, name_add_max)
local name_panorama = weapon.console_name:gsub("^item_", ""):gsub("^weapon_", "")
-- align pistols
if weapon.type == "pistol" then
js.set_min_max_width(name_panorama, "31px") -- 29px
elseif weapon.type == "knife" and weapon ~= WEAPON_TASER then
js.set_min_max_width(name_panorama, "45px", "45px", "smaller")
end
end
-- fix knife icons
js.set_min_max_width("knife", nil, nil, "small")
js.set_min_max_width("knife_t", nil, nil, "small")
js.set_min_max_width("knife_widowmaker", nil, nil, "small")
js.set_min_max_width("knife_butterfly", nil, nil, "small")
js.set_min_max_width("knife_survival_bowie", nil, nil, "large")
js.set_min_max_width("knife_gut", nil, nil, "medium")
js.set_min_max_width("knife_karambit", nil, nil, "medium")
js.set_min_max_width("knife_ursus", nil, nil, "small")
js.set_min_max_width("hkp2000", nil, nil, "medium")
-- grenades
js.set_min_max_width("incgrenade", "12px")
js.set_min_max_width("smokegrenade", "9px")
js.set_min_max_width("flashbang", "9px", "12px")
for idx, weapon in pairs(csgo_weapons) do
if sort_pos[weapon] == nil then
local name_add = name_add_weapon[weapon] / name_add_max
if weapon.type == "rifle" or weapon.type == "machinegun" or weapon.type == "sniperrifle" or weapon.type == "smg" or weapon.type == "shotgun" then
sort_pos[weapon] = 0+name_add
elseif weapon.type == "pistol" then
sort_pos[weapon] = 1+name_add
elseif weapon.type == "knife" or weapon.type == "fists" or weapon.type == "melee" then
sort_pos[weapon] = 2+name_add
else
-- print(weapon.console_name, " ", weapon.type)
sort_pos[weapon] = weapon.idx
end
end
end
local function sort_weapons_cb(a, b)
local a_i = sort_pos[a] or a.idx
local b_i = sort_pos[b] or b.idx
return a_i < b_i
end
--
-- actual script logic
--
local enabled_reference = ui.new_checkbox("VISUALS", "Other ESP", "Display equipment on scoreboard")
local filter_reference = ui.new_multiselect("VISUALS", "Other ESP", "\nScoreboard equipment filter", {"Primary", "Secondary", "Knife", "Taser", "Grenades", "Bomb", "Defuse Kit", "Armor", "Other"})
local enemy_only_reference = ui.new_checkbox("VISUALS", "Other ESP", "Enemies only")
local auto_unmute_reference = ui.new_multiselect("VISUALS", "Other ESP", "Auto unmute players", {"Self", "Friends", "All players"})
ui.set(filter_reference, {"Primary", "Secondary", "Grenades", "Bomb"})
local player_data = {}
local filter_weapon_name = {}
local filter_armor_enabled = false
local enabled_prev = false
local function filter_cb(weapon)
return filter_weapon_name[weapon]
end
local function update_player_data(player)
-- print("update_player_data(", player, ")")
local current_player_data = player_data[player]
local ignore_teammate = ui.get(enemy_only_reference) and not entity.is_dormant(player) and not entity.is_enemy(player)
local player_scoreboard = player
local player_resource = entity.get_player_resource()
if entity.get_prop(player_resource, "m_bControllingBot", player) == 1 then
player_scoreboard = entity.get_prop(player_resource, "m_iControlledPlayer", player)
js.update_player(player, nil, nil, nil)
-- print(player, " is controlling ", player_scoreboard)
end
if current_player_data == nil or ignore_teammate then
js.update_player(player_scoreboard, nil, nil, nil)
else
js.update_player(
player_scoreboard,
current_player_data.weapons and table_map_filter(current_player_data.weapons, filter_cb) or nil,
current_player_data.active_weapon and filter_weapon_name[current_player_data.active_weapon] or nil,
filter_armor_enabled and current_player_data.armor or nil
)
end
end
local function update_filters()
table_clear(filter_weapon_name)
if ui.get(enabled_reference) then
local filters_enabled = table_map_assoc(ui.get(filter_reference), function(i, typ) return typ, true end)
filter_armor_enabled = filters_enabled["Armor"]
local team
local local_player = entity.get_local_player()
if local_player ~= nil then
team = entity.get_prop(local_player, "m_iTeamNum")
end
for idx, weapon in pairs(csgo_weapons) do
local include = false
-- print(weapon.console_name, ": ", weapon.type)
if weapon.type == "rifle" or weapon.type == "machinegun" or weapon.type == "sniperrifle" or weapon.type == "smg" or weapon.type == "shotgun" then
include = filters_enabled["Primary"]
elseif weapon.type == "pistol" then
include = filters_enabled["Secondary"]
elseif weapon == WEAPON_TASER then
include = filters_enabled["Taser"]
elseif weapon.type == "c4" then
include = team ~= TEAM_T and filters_enabled["Bomb"]
elseif weapon == ITEM_CUTTERS or weapon == ITEM_DEFUSER then
include = team ~= TEAM_CT and filters_enabled["Defuse Kit"]
elseif weapon.type == "knife" or weapon.type == "fists" or weapon.type == "melee" then
include = filters_enabled["Knife"]
elseif weapon.type == "grenade" or weapon.type == "breachcharge" then
include = filters_enabled["Grenades"]
elseif weapon ~= ITEM_ASSAULTSUIT and weapon ~= ITEM_KEVLAR and weapon ~= ITEM_HEAVYASSAULTSUIT then
include = filters_enabled["Other"]
end
if include then
filter_weapon_name[weapon] = weapon.console_name:gsub("^item_", ""):gsub("^weapon_", "")
end
end
for player, data in pairs(player_data) do
update_player_data(player)
end
end
end
--
-- event callbacks
--
local function on_paint()
run_pending_callbacks()
local player_resource = entity.get_player_resource()
local free_kevlar = cvar.mp_free_armor:get_int() > 0
local free_helmet = cvar.mp_free_armor:get_int() > 1
local free_defuser = cvar.mp_defuser_allocation:get_int() >= 2
for player=1, 64 do
if entity.get_classname(player) == "CCSPlayer" then
local current_player_data
if not entity.is_dormant(player) then
if entity.is_alive(player) then
current_player_data = {
weapons = {}
}
local active_weapon = entity.get_player_weapon(player)
if active_weapon ~= nil then
if not free_defuser and entity.get_prop(player, "m_bHasDefuser") == 1 then
table.insert(current_player_data.weapons, ITEM_DEFUSER)
end
for slot=0, 63 do
local weapon_ent = entity.get_prop(player, "m_hMyWeapons", slot)
if weapon_ent ~= nil then
local weapon = csgo_weapons[entity.get_prop(weapon_ent, "m_iItemDefinitionIndex")]
table.insert(current_player_data.weapons, weapon)
if weapon_ent == active_weapon then
current_player_data.active_weapon = weapon
end
end
end
table.sort(current_player_data.weapons, sort_weapons_cb)
end
else
current_player_data = nil
end
else
current_player_data = player_data[player]
end
if current_player_data ~= nil then
if entity.get_prop(player_resource, "m_iArmor", player) > 0 then
if entity.get_prop(player_resource, "m_bHasHelmet", player) == 1 then
if not free_helmet then
current_player_data.armor = "helmet"
end
elseif not free_kevlar then
current_player_data.armor = "kevlar"
end
else
current_player_data.armor = nil
end
end
if (player_data[player] == nil and current_player_data ~= nil) or (current_player_data == nil and player_data[player] ~= nil) or (current_player_data ~= nil and player_data[player] ~= nil and not deep_compare(current_player_data, player_data[player])) then
player_data[player] = current_player_data
update_player_data(player)
end
end
end
end
local function on_shutdown()
if enabled_prev then
js.destroy()
end
end
local function on_level_init()
table_clear(player_data)
js.clear()
end
local function on_player_team(e)
local player = client.userid_to_entindex(e.userid)
if player == entity.get_local_player() then
client.delay_call(0.1, update_filters)
elseif player > 0 then
-- update_filters will already call update_player_data for everyone
update_player_data(player)
end
end
--
-- game event callbacks for dormant data
--
local function on_player_disconnect(e)
local player = client.userid_to_entindex(e.userid)
player_data[player] = nil
update_player_data(player)
end
local function on_player_death(e)
local player = client.userid_to_entindex(e.userid)
if player_data[player] ~= nil and entity.is_dormant(player) then
player_data[player] = nil
update_player_data(player)
end
end
local function on_player_spawn(e)
local player = client.userid_to_entindex(e.userid)
if player_data[player] == nil then
player_data[player] = {
weapons = {}
}
elseif player_data[player].weapons ~= nil then
table_remove_item(player_data[player].weapons, WEAPON_C4)
end
update_player_data(player)
end
local function on_item_remove(e)
local player = client.userid_to_entindex(e.userid)
local weapon = EVENT_IDX_TO_WEAPON[e.defindex]
if player_data[player] ~= nil and entity.is_dormant(player) and weapon then
if weapon ~= ITEM_KEVLAR and weapon ~= ITEM_ASSAULTSUIT then
table_remove_item(player_data[player].weapons, weapon)
update_player_data(player)
end
end
end
local function on_item_pickup(e)
local player = client.userid_to_entindex(e.userid)
local weapon = EVENT_IDX_TO_WEAPON[e.defindex]
if player_data[player] ~= nil and entity.is_dormant(player) and weapon then
if weapon == ITEM_KEVLAR or weapon == ITEM_ASSAULTSUIT then
local free_kevlar = cvar.mp_free_armor:get_int() > 0
local free_helmet = cvar.mp_free_armor:get_int() > 1
if weapon == ITEM_KEVLAR then
if not free_helmet and player_data[player].armor == nil then
player_data[player].armor = "kevlar"
end
elseif not free_kevlar then
player_data[player].armor = "helmet"
end
elseif (weapon == ITEM_CUTTERS or weapon == ITEM_DEFUSER) and cvar.mp_defuser_allocation:get_int() >= 2 then
return
elseif not table_contains(player_data[player].weapons, weapon) then
table.insert(player_data[player].weapons, weapon)
table.sort(player_data[player].weapons, sort_weapons_cb)
update_player_data(player)
end
end
end
local function on_item_equip(e)
local player = client.userid_to_entindex(e.userid)
local weapon = EVENT_IDX_TO_WEAPON[e.defindex]
if player_data[player] ~= nil and entity.is_dormant(player) and weapon then
player_data[player].active_weapon = weapon
update_player_data(player)
end
end
local function on_bot_takeover(e)
local player = client.userid_to_entindex(e.userid)
local bot = client.userid_to_entindex(e.botid)
local player_resource = entity.get_player_resource()
entity.set_prop(player_resource, "m_bControllingBot", 1, player)
entity.set_prop(player_resource, "m_iControlledPlayer", bot, player)
-- print("takeover -> update_player_data")
update_player_data(bot)
update_player_data(player)
end
local function on_enabled_changed()
local enabled = ui.get(enabled_reference)
ui.set_visible(filter_reference, enabled)
ui.set_visible(enemy_only_reference, enabled)
if enabled and not enabled_prev then
client.set_event_callback("paint", on_paint)
client.set_event_callback("shutdown", on_shutdown)
client.set_event_callback("level_init", on_level_init)
client.set_event_callback("player_team", on_player_team)
add_delayed_callback("player_disconnect", on_player_disconnect)
add_delayed_callback("player_death", on_player_death)
add_delayed_callback("player_spawn", on_player_spawn)
add_delayed_callback("item_remove", on_item_remove)
add_delayed_callback("item_pickup", on_item_pickup)
add_delayed_callback("item_equip", on_item_equip)
add_delayed_callback("bot_takeover", on_bot_takeover)
update_filters()
js.create()
elseif not enabled and enabled_prev then
client.unset_event_callback("paint", on_paint)
client.unset_event_callback("shutdown", on_shutdown)
client.unset_event_callback("level_init", on_level_init)
client.unset_event_callback("player_team", on_player_team)
clear_delayed_callbacks()
table_clear(player_data)
table_clear(filter_weapon_name)