-
Notifications
You must be signed in to change notification settings - Fork 2
/
sheetBot.js
2698 lines (2647 loc) · 138 KB
/
sheetBot.js
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
const Discord = require('discord.js');
const client = new Discord.Client({ intents: ["GUILDS", "GUILD_MESSAGES"] })
const axios = require('axios').default; // This handles URL loads / page calls
const readline = require('readline');
const { MessageEmbed } = require('discord.js');
const { table, getBorderCharacters } = require('table')
const QuickChart = require('quickchart-js');
const fs = require('fs'); // Allows node.js to use the file system.
const { ClientRequest } = require('http');
const config = JSON.parse(fs.readFileSync("./config.json", "utf-8"))
var generalChannel = config.generalChannelID
var prefixes = {};
var apmweight = 1 // All of the below are weights to do with the versus graph area and the area stat.
var ppsweight = 45
var vsweight = 0.444
var appweight = 185
var dssweight = 175
var dspweight = 450
var dsappweight = 140
var vsapmweight = 60
var ciweight = 1.25
var geweight = 315
var apmsrw = 0 // All of the below are weights for the stat rank (or sr) stat and the esttr / estglicko variables.
var ppssrw = 135
var vssrw = 0
var appsrw = 290
var dsssrw = 0
var dspsrw = 700
var garbageeffisrw = 0
var playerCount = 0 // The total ranked player count, not including averages or unranked.
var unrankedCount = 0 // The total number of unranked players grabbed from the unranked players list.
var tawsPrefix = "s!"
let latamCountries = ["AR", "BO", "BR", "CL", "CO", "CR", "CU", "DO", "EC", "SV", "GT", "HN", "MX", "NI", "PA", "PY", "PE", "PT", "PR", "UY", "VE"]
let euCountries = ["AD", "AL", "AM", "AT", "AZ", "BE", "BG", "BY", "CH", "CY", "CZ", "DE", "DK", "EE", "ES", "EU", "FO", "FI", "FR", "GE", "GI", "GG", "GB", "GB-ENG", "GB-NIR", "GB-SCT", "GB-WLS", "GR", "HR", "HU", "IE", "IM", "IS", "IT", "JE", "LI", "LT", "LU", "LV", "MC", "ME", "MD", "MT", "NL", "NO", "NZ", "PL", "PT", "RO", "RU", "SE", "SI", "SM", "SR", "TR", "UA", "VA", "XK"]
const unrankedPlayers = './unrankedPlayers.txt' // Holds all of the unranked players
const prefixFile = "./prefix.txt" // Holds the prefix used for TAWS.
var pList = [] // The list of all players (including unranked, not including average players.)
var avgPlayers = [] // Choosing to store these separately because of commands like !lb that'll try to go through all players.
// You could just stick it at the end of pList though most likely.
var rankArray = ["x+", "x", "u", "ss", "s+", "s", "s-", "a+", "a", "a-", "b+", "b", "b-", "c+", "c", "c-", "d+", "d", "z"]
// Array of the ranks, in order
var loopCount = 1;
class Player {
constructor(name, apm, pps, vs, tr, glicko, rd, data) {
/*
I originally wanted to include two different constructors for this, one that uses data and one that doesn't, but
I discovered that JS doesn't support it :/
There's hacky ways to get around it but eh, whatever.
*/
this.name = name
this.apm = Number(apm) // We define these as numbers here because if we do something like name[0] later on to fill
// these in, it will assume that it's a string since name is an array of strings. This changes it to a number.
this.pps = Number(pps)
this.vs = Number(vs)
this.tr = tr
this.glicko = glicko
this.rd = rd
this.app = (this.apm / 60 / this.pps)
this.dss = (this.vs / 100) - (this.apm / 60)
this.dsp = this.dss / this.pps
this.dsapp = this.dsp + this.app
this.vsapm = (this.vs / this.apm)
this.ci = (this.dsp * 150) + ((this.vsapm - 2) * 50) + ((0.6 - this.app) * 125)
this.ge = ((this.app * this.dss) / this.pps) * 2
this.wapp = (this.app - 5 * Math.tan(((this.ci / -30) + 1) * Math.PI / 180))
this.area = this.apm * apmweight + this.pps * ppsweight + this.vs * vsweight + this.app * appweight + this.dss * dssweight + this.dsp * dspweight + this.ge * geweight
this.srarea = (this.apm * apmsrw) + (this.pps * ppssrw) + (this.vs * vssrw) + (this.app * appsrw) + (this.dss * dsssrw) + (this.dsp * dspsrw) + (this.ge * garbageeffisrw)
this.sr = (11.2 * Math.atan((this.srarea - 93) / 130)) + 1
if (this.sr <= 0) {
this.sr = 0.001
}
//this.estglicko = (4.0867 * this.srarea + 186.68)
this.estglicko = (0.000013 * (((this.pps * (150 + ((this.vsapm - 1.66) * 35)) + this.app * 290 + this.dsp * 700)) ** 3) - 0.0196 * (((this.pps * (150 + ((this.vsapm - 1.66) * 35)) + this.app * 290 + this.dsp * 700)) ** 2) + (12.645 * ((this.pps * (150 + ((this.vsapm - 1.66) * 35)) + this.app * 290 + this.dsp * 700))) - 1005.4)
this.esttr = 25000 / (1 + 10 ** (((1500 - this.estglicko) * Math.PI) / (Math.sqrt(((3 * Math.log(10) ** 2) * 60 ** 2) + (2500 * ((64 * Math.PI ** 2) + (147 * Math.log(10) ** 2)))))))
//this.esttr = Number(Number(25000 / (1 + (10 ** (((1500 - ((4.0867 * this.srarea + 186.68))) * 3.14159) / (((15.9056943314 * (this.rd ** 2) + 3527584.25978) ** 0.5)))))).toFixed(2))
// ^ Estimated TR
this.atr = this.esttr - this.tr // Accuracy of TR Estimate
//this.aglicko = this.estglicko - this.glicko
this.opener = Number(Number(Number((((this.apm / this.srarea) / ((0.069 * 1.0017 ** ((this.sr ** 5) / 4700)) + this.sr / 360) - 1) + (((this.pps / this.srarea) / (0.0084264 * (2.14 ** (-2 * (this.sr / 2.7 + 1.03))) - this.sr / 5750 + 0.0067) - 1) * 0.75) + (((this.vsapm / (-(((this.sr - 16) / 36) ** 2) + 2.133) - 1)) * -10) + ((this.app / (0.1368803292 * 1.0024 ** ((this.sr ** 5) / 2800) + this.sr / 54) - 1) * 0.75) + ((this.dsp / (0.02136327583 * (14 ** ((this.sr - 14.75) / 3.9)) + this.sr / 152 + 0.022) - 1) * -0.25)) / 3.5) + 0.5).toFixed(4))
this.plonk = Number(Number((((this.ge / (this.sr / 350 + 0.005948424455 * 3.8 ** ((this.sr - 6.1) / 4) + 0.006) - 1) + (this.app / (0.1368803292 * 1.0024 ** ((this.sr ** 5) / 2800) + this.sr / 54) - 1) + ((this.dsp / (0.02136327583 * (14 ** ((this.sr - 14.75) / 3.9)) + this.sr / 152 + 0.022) - 1) * 0.75) + (((this.pps / this.srarea) / (0.0084264 * (2.14 ** (-2 * (this.sr / 2.7 + 1.03))) - this.sr / 5750 + 0.0067) - 1) * -1)) / 2.73) + 0.5).toFixed(4))
this.stride = Number(Number((((((this.apm / this.srarea) / ((0.069 * 1.0017 ** ((this.sr ** 5) / 4700)) + this.sr / 360) - 1) * -0.25) + ((this.pps / this.srarea) / (0.0084264 * (2.14 ** (-2 * (this.sr / 2.7 + 1.03))) - this.sr / 5750 + 0.0067) - 1) + ((this.app / (0.1368803292 * 1.0024 ** ((this.sr ** 5) / 2800) + this.sr / 54) - 1) * -2) + ((this.dsp / (0.02136327583 * (14 ** ((this.sr - 14.75) / 3.9)) + this.sr / 152 + 0.022) - 1) * -0.5)) * 0.79) + 0.5).toFixed(4))
this.infds = Number(Number((((this.dsp / (0.02136327583 * (14 ** ((this.sr - 14.75) / 3.9)) + this.sr / 152 + 0.022) - 1) + ((this.app / (0.1368803292 * 1.0024 ** ((this.sr ** 5) / 2800) + this.sr / 54) - 1) * -0.75) + (((this.apm / this.srarea) / ((0.069 * 1.0017 ** ((this.sr ** 5) / 4700)) + this.sr / 360) - 1) * 0.5) + ((this.vsapm / (-(((this.sr - 16) / 36) ** 2) + 2.133) - 1) * 1.5) + (((this.pps / this.srarea) / (0.0084264 * (2.14 ** (-2 * (this.sr / 2.7 + 1.03))) - this.sr / 5750 + 0.0067) - 1) * 0.5)) * 0.9) + 0.5).toFixed(4))
if (data != null) { // If we have the individual data for this player...
// Assign all of it
this.id = data._id
// ID is now only assigned in normal user call without the summary. Temporarily removed this to reduce API calls
if (data.hasOwnProperty("rank")) {
this.rank = data.rank
} else {
this.rank = data.league.rank
}
//this.percent_rank = data.league.percentile_rank // Pretty much just used for !avg
this.country = data.country
if (data.hasOwnProperty("league")) {
this.games = data.league.gamesplayed
this.wins = data.league.gameswon
this.wr = (this.wins / this.games) * 100 // TL winrate
} else {
this.games = data.gamesplayed
this.wins = data.gameswon
this.wr = (this.wins / this.games) * 100 // TL winrate
}
this.avatar = data.avatar_revision
// this.position = pList.map(pList => pList.name).indexOf(this.name)
// The above works but it was horrifically slow.
} else { // Otherwise...
// Put dummy data in.
this.id = -1
this.rank = null
this.position = pList.length + 1
this.country = null // In-game country (conv to string to prevent null error)
this.games = -1 // TL games played
this.wins = -1 // TL wins
this.wr = -1 // TL winrate
}
}
}
client.on('ready', () => {
console.log("Connected as " + client.user.tag);
//console.log(Discord.version)
client.user.setActivity("Loading...")
})
/*
async function getData() { // Load the initial data. This is used for !lb, !avg, !ac, basically anything where you need a lot of players.
try {
let res = await axios({
url: 'https://ch.tetr.io/api/users/by/league?limit=100', // Call the link that has all the ranked league players in it.
method: 'get',
})
// Don't forget to return something
var value = (res.data)
console.log("Data fetched!")
return value
}
catch (err) { // Error catching.
console.error(err);
}
}
*/
async function getJSON(url) {
console.log("Current URL is: " + url)
return fetch(url) // Fetch the URL
.then((j) => j.json()) // Then wait for response
.then((JaSON) => { return JaSON }); // And return the response JSON
}
async function getData(url) {
const json = await getJSON(url); // Call getJSON, wait until done
if (loopCount == 0) {
return json;
} else {
return assign(json) // Then return to assign
}
}
/* This is basic fetch code but doesn't work well when iterated
async function getData(url) {
console.log("Current URL is: " + url)
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Response status: ${response.status}`);
}
const json = await response.json();
console.log(json);
return json.then()
} catch (error) {
console.error(error.message);
}
}
*/
getData("https://ch.tetr.io/api/users/by/league?limit=100") // Call the getData function
//.then(value => assign(value)) // Actually do something with the data you've grabbed after you've grabbed it.
async function assign(value) { // This assigns all the data that you've loaded to the different variables.
let generalChannelLocal = generalChannel // Set the general channel.
playerCount = value.data.entries.length // Set the search length to the number of ranked users.
console.log("Assign data is running.")
for (let i = 0; i < playerCount; i++) {
if (value.data.entries[i].league.apm != null) { // If the player's APM isn't null (meaning they exist and aren't banned somehow)
var tmp = new Player(value.data.entries[i].username.replace(/['"]+/g, ''), // Make a new player by passing the following
value.data.entries[i].league.apm, // APM
value.data.entries[i].league.pps, // PPS
value.data.entries[i].league.vs, // VS
value.data.entries[i].league.tr, // TR
value.data.entries[i].league.glicko, // Glicko
value.data.entries[i].league.rd, // RD
value.data.entries[i] // The whole of the data for each user.
)
// Then add the following to the class for each player since they aren't built in.
tmp.position = i + 1 + (100 * (loopCount - 1)) // Position on the TL leaderboard
pList.push(tmp) // Push this player to the player list.
} else { // Otherwise, remove them from the list entirely and decrement the loop.
value.data.entries.splice(i, 1);
playerCount -= 1;
i -= 1;
}
}
//console.log(pList.map(pList => pList.apm))
//console.log(pList.map(pList => pList.country))
//fs.writeFile("./stats/all.txt", JSON.stringify(pList), (err) => { if (err) throw err; })
//w("apm"); w("pps"); w("vs"); w("app"); w("dsp"); w("dss"); w("dsapp"); w("vsapm"); w("glicko"); w("area"); w("srarea"); w("estglicko"); w("atr"); w("position"); w("estglicko"); //w("aglicko") // Log a bunch of stats
//console.log(g("pps"))
console.log("The player count is: " + pList.length + ", with " + playerCount + " just added.")
if (playerCount == 100) {
console.log("The last player left off on is: " + pList[(100 * loopCount) - 1].name)
getData("https://ch.tetr.io/api/users/by/league?after=" + pList[(100 * loopCount) - 1].tr + ":0:0&limit=100")
loopCount += 1;
} else {
//fetchUnranked()
loadPrefixes()
}
}
function w(stat) { // w for write This is simply a conveinient shorthand.
fs.writeFile("./stats/" + stat + ".txt", pList.map(pList => pList[stat]).join("\n"), (err) => { if (err) throw err; })
}
function g(stat) { // g for grab or give.
return pList.map(pList => pList[stat])
}
/*
async function fetchUnranked() { // Fetch the unranked players based on unrankedPlayers.txt
var ids = []
var contents = []
var nameList = []
var skip = 2
const rl = readline.createInterface({
input: fs.createReadStream(unrankedPlayers),
output: process.stdout,
terminal: false
});
for await (const line of rl) {
if (skip == 1 || skip == 2) {
ids.push(line + "\n") // Add this to the end of the ID array. The ID array holds all the IDs of the players.
}
if (skip == 0 || skip == 2) {
nameList.push(line + "\n") // Add this to the end of the nameList array. The nameList array holds the names of all the players.
}
contents.push(line + "\n") // For the contents as a whole, add both the IDs and names.
if (skip == 1) { // A simple switch back-and-forth toggle.
skip = 0
} else {
skip = 1
}
}
// nameList.splice(0, 1);
const promises = [];
for (let i = 0; i < Number(ids.length); i++) { // Loop through the list and grab stats using the same method as before.
if (i != 0) {
try {
promises.push(axios({
url: 'https://ch.tetr.io/api/users/' + String(ids[i]),
method: 'get',
}))
}
catch (err) { // In case the data fails to load for whatever reason.
console.error(err);
}
}
}
const results = await Promise.allSettled(promises);
for (let y = 0; y < results.length; ++y) {
try {
const result = results[y];
if (result.status == "rejected") {
console.error(result.reason);
continue;
}
let res = result.value;
value = (res.data);
let i = y + 1;
// This basically does the same thing as the assign function, but for unranked players.
if (value.success == false || value.data.rank != "z" || value.data.apm == null) { // If the user is no longer unranked
// or is banned / deleted
// if (i > -1) {
console.log(nameList[i] + " (" + i + ", with id:" + ids[i - 1] + " ) was removed!\nTheir rank is: " + value.data.rank + ", and their apm is " + value.data.apm)
nameList.splice(i, 1)
ids.splice(i - 1, 1)
contents.splice((i * 2) - 1, 2)
results.splice(i - 1, 1);
fs.writeFile(unrankedPlayers, contents.join(""), (err) => { if (err) throw err; })
y -= 1;
continue;
// }
}
console.log(nameList[i])
var tmp = new Player(value.data.past["1"].username, // Make a new player by passing the following
value.data.apm, // APM
value.data.pps, // PPS
value.data.vs, // VS
value.data.tr, // TR
value.data.glicko, // Glicko
value.data.rd, // RD
value.data // The whole of the data for the player.
)
tmp.position = 0
pList.push(tmp)
// Since we have the whole data being sent, no need to add the things that aren't automatically included.
unrankedCount += 1; // Then add one to the unranked player count
}
catch (e) { // In case the data fails to load for whatever reason.
console.error(e);
}
}
loadPrefixes();
}
*/
async function loadPrefixes() {
const prefixData = fs.readFileSync("./prefix.json", "utf-8");
prefixes = JSON.parse(prefixData);
taws(); // The taws function is probably not needed anymore. but just in case i'll keep it here.
}
async function taws() {
const read = readline.createInterface({
input: fs.createReadStream(prefixFile),
output: process.stdout,
terminal: false
});
for await (const line of read) {
tawsPrefix = String(line) // Change the prefix based on whatever they want it to be. The function that goes to this is now disabled since it's already been set up.
}
averagePlayers()
}
async function averagePlayers() {
let generalChannelLocal = generalChannel
var rankCount = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // Count the number of players in each rank
var ranks = g("rank")
console.log(pList.slice(-unrankedCount))
console.log(ranks.slice(-unrankedCount))
playerNames = [] // The names of the players, which will be assigned shortly.
avgPlayers = []
totPlayersSort = 0 // Total number of players we've sorted through so far
for (let i = 0; i < ranks.length; i++) {
for (let j = 0; j < rankArray.length; j++) {
if (ranks[i] == rankArray[j]) {
rankCount[j] += 1
}
}
}
for (let i = 0; i < rankArray.length; i++) {
playerNames.push("$avg" + rankArray[i].toUpperCase())
let tmp = new Player(playerNames[i],
g("apm").slice(totPlayersSort, totPlayersSort + rankCount[i]).reduce((a, b) => a + b, 0) / rankCount[i],
g("pps").slice(totPlayersSort, totPlayersSort + rankCount[i]).reduce((a, b) => a + b, 0) / rankCount[i],
g("vs").slice(totPlayersSort, totPlayersSort + rankCount[i]).reduce((a, b) => a + b, 0) / rankCount[i],
g("tr").slice(totPlayersSort, totPlayersSort + rankCount[i]).reduce((a, b) => a + b, 0) / rankCount[i],
g("glicko").slice(totPlayersSort, totPlayersSort + rankCount[i]).reduce((a, b) => a + b, 0) / rankCount[i],
g("rd").slice(totPlayersSort, totPlayersSort + rankCount[i]).reduce((a, b) => a + b, 0) / rankCount[i],
null
)
tmp.id = 0
tmp.rank = "h" // I really just did this because it was funny. That's about it.
// It's also a way of differentiating from other ranks if you ever wanted to put the average players in the
// pList array with every other player- simply filter out the h ranks.
tmp.position = -i
totPlayersSort += rankCount[i]
avgPlayers.push(tmp)
//console.log(totPlayersSort)
client.user.setActivity("on " + g("name")[(Math.floor(Math.random() * pList.length - 1))] + "'s account")
}
playerNames.push("$avgAL") // Add average of all players as a parameter. This isn't made as "$avgALL" due to some jank code with !ts
let tmp = new Player("$avgAL",
g("apm").reduce((a, b) => a + b, 0) / pList.length,
g("pps").reduce((a, b) => a + b, 0) / pList.length,
g("vs").reduce((a, b) => a + b, 0) / pList.length,
g("tr").reduce((a, b) => a + b, 0) / pList.length,
g("glicko").reduce((a, b) => a + b, 0) / pList.length,
g("rd").reduce((a, b) => a + b, 0) / pList.length,
null
)
tmp.id = 0
tmp.rank = "h"
tmp.position = -18 // Add to end of normal positions
avgPlayers.push(tmp)
rankArray.push("al")
console.log(rankCount)
console.log(avgPlayers)
let guild = await client.guilds.fetch(config.yourGuildID);
let channel = await guild.channels.fetch(config.generalChannelID);
await channel.send("Ready!");
loopCount = 0
console.log(g("name"))
}
everythingElse()
function everythingElse() {
client.on('message', (text) => {
if (text.author == client.user) { // Prevent bot from responding to its own messages
return
}
generalChannel = text.channelId // Set generalChannel to whatever channel the message is currently in.
const guildId = text.guild != undefined ? text.guild.id : "";
const guildPrefix = prefixes[guildId];
const prefix = guildPrefix == undefined ? config.defaultPrefix : guildPrefix;
// If the bot is pinged, give its prefix.
const botMention = `<@${client.user.id}>`;
if (text.content.startsWith(botMention)) {
prefixcommand([], prefix, text)
return;
}
// Instead of having to modify the code to add a server's prefix,
// this will enable servers to define the prefix
// they want for their server with a command.
if (text.content.startsWith(prefix)
// && text.guild.id != "599005375907495936"
) { // ! is the prefix used for commands. You could change this if you wanted to.
processCommand(text, prefix)
}
// if (text.content.startsWith(tawsPrefix) && text.guild.id == "599005375907495936") { // Again, they wanted a different prefix for that server.
// processCommand(text)
// }
})
function processCommand(text, prefix = '!') {
client.user.setActivity("on " + g("name")[(Math.floor(Math.random() * pList.length - 1))] + "'s account")
let parsedText = ""
// if (text.guild.id == "599005375907495936") {
// parsedText = text.content.substr(tawsPrefix.length) // Remove the leading exclamation mark
// } else {
// parsedText = text.content.substr(1)
// }
parsedText = text.content.substr(prefix.length)
let spacesText = parsedText.split(" ") // Split the message up in to pieces for each space
let command = spacesText[0] // The first word directly after the exclamation is the command
if (command.length != 0) {
command = command.toLowerCase()
}
let name = spacesText.slice(1) // All other words are name/parameters/options for the command
for (let i = 0; i < name.length; i++) {
if (isNaN(Number(name[i]))) { // If it's not a number
name[i] = name[i].toLowerCase()
name[i] = name[i].replace(/[\u{0080}-\u{FFFF}]/gu, '') // Removes unicode characters
name[i] = name[i].replace(/[&\/\\#,()~%'":*?{}]/g, '') // Removes some other problematic characters
}
}
console.log(name)
console.log(command)
if (name.length <= 0 && (command == "ts" || command == "vs" || command == "vsr" || command == "o" || command == "vst" || command == "psq"
|| command == "sq" || command == "ac" || command == "lb" || command == "rlb" || command == "z" || command == "rnk" || command == "avg"
|| command == "med")) {
return client.channels.cache.get(text.channelId).send("Too few parameters entered.")
}
// Different commands are called below. Self-explanatory.
if (command == "prefix") { // DONE
prefixcommand(parsedText.split(" ").slice(1), prefix, text)
// Note: For a prefix command you don't need to filter problematic characters
}
if (command == "ts") { // DONE?
tetostat(name)
}
if (command == "help") { // Not Started
help(name)
}
if (command == "vs") { // DONE
versus(name, false, false) // First is for relative, second is for tableValue
}
if (command == "vst") { // DONE
versus(name, false, true)
}
if (command == "vsr") { // Done
versus(name, true, false)
}
if (command == "o") { // Done, but should add a feature to sort by other stats than TR/position in league
operate(name)
}
if (command == "psq") { // Done
triangle(name, true)
}
if (command == "sq") { // Done, perhaps change blue color to be more visible then rest.
triangle(name, false) // Also strange issue with embeds not working when putting in stats?
}
if (command == "ac") { // DONE
allcomp(name)
}
if (command == "lb") {
leaderboard(name, false) // False is for setting reverse
}
if (command == "rlb") {
leaderboard(name, true)
}
if (command == "avg") { // DONE, two minor bugs relating to number grabbing one under or over the right count
getAverage(name, false) // And saying "Average rank of rank UNDEFINED"
// False here is for median
}
if (command == "med") {
getAverage(name, true)
}
if (command == "cc") { // DONE
copycat(name)
}
if (command == "refresh") { // Not started
refresh()
}
if (command == "z") { // DONE?
addUnranked(name)
}
if (command == "rnk") { // Not started
rankStat(name)
}
/*
if (command == "prefix") { // Again, disabled because it already got set.
prefix(name)
}
*/
}
}
async function prefixcommand(name, prefix, text) {
let generalChannelLocal = generalChannel;
let channel = client.channels.cache.get(generalChannelLocal);
const guild = text.guild;
if (guild == undefined) {
channel.send(`This bot's prefix is ${prefix}.`);
return;
}
if (name[0] != undefined) {
name[0] = String(name[0]);
}
else {
channel.send(`This server's prefix is \`${prefix}\`\nYou can change it with \`${prefix}prefix [prefix]\``);
return;
}
if (!text.member.permissions.has('ADMINISTRATOR')) {
channel.send("You do not have the permission to change the prefix for that server. You need `ADMINISTRATOR` permissions.");
return;
}
prefixes[guild.id] = name[0];
fs.writeFileSync("./prefix.json", JSON.stringify(prefixes, undefined, 4), "utf-8");
console.log(prefixes)
channel.send(`This server's prefix has been changed to ${name[0]}`);
}
async function help(name) {
let generalChannelLocal = generalChannel;
let helpContent;
if (name[0] != undefined) {
name[0] = String(name[0]).toLowerCase()
} else {
helpContent = "List of commands: `ts, vs, vst, vsr, sq, psq, lb, rlb, ac, cc, avg, med, o, z, refresh, rnk` \nUse `!help [command]` for more info on any specific command."
+ "\n" + "You can also type `!help calcs` for calculation info."
}
if (name[0] == "calc" || name[0] == "!calc" || name[0] == "calculations" || name[0] == "!calculations" || name[0] == "calcs" || name[0] == "!calcs") {
helpContent = "Calculations are performed as follows: \n" + "APP: `APM/(PPS*60)` \n" + "DS/Second: `(VS/100)-(APM/60)` \n" + "DS/Piece: `((VS/100)-(APM/60))/PPS` \n" + "APP+DS/Piece: `(((VS/100)-(APM/60))/PPS) + APM/(PPS*60)` \n" + "Cheese Index: `((DS/Piece * 150) + (((VS/APM)-2)*50) + (0.6-APP)*125)` \n" + "Garbage Effi.: `(attack*downstack)/pieces^2` \n" + "Area: `apm + pps * 45 + vs * 0.444 + app * 185 + dssecond * 175 + dspiece * 450 + garbageEffi * 315` \n Weighted APP: `APP - 5 * tan((cheeseIndex/ -30) + 1)` \n Est. TR: `25000/(1+10^(((1500-(0.000013*(((pps * (150 + ((vsapm - 1.66) * 35)) + app * 290 + dspiece * 700))^3) - 0.0196*(((pps * (150 + ((vsapm - 1.66) * 35)) + app * 290 + dspiece * 700))^2) + (12.645*((pps * (150 + ((vsapm - 1.66) * 35)) + app * 290 + dspiece * 700))) - 1005.4))*pi)/(sqrt(((3*ln(10)^2)*60^2)+(2500*((64*pi^2)+(147*ln(10)^2)))))))`"
}
if (name[0] == "ts" || name[0] == "!ts") {
helpContent = "!ts - Displays stats of a user in a table list.\n"
+ "**Usage**: `!ts [username]` or `!ts [apm] [pps] [vs]`\n"
+ "**Extra Parameters:** `-m`: To be added at the end of the command. (Ex: `!ts explorat0ri -m`). Will display a more minimal output with less stats and clutter."
+ "\n(*This command supports the use of average players. To use, simply type `$avg[rank]` where a player would be entered.*)"
}
if (name[0] == "vs" || name[0] == "!vs") {
helpContent = "`!vs` - Compares the stats of two users (or one, if you input one name twice), in a more complicated version of the !sq command radar graph with more stats shown.\n"
+ "**Usage** - `!vs [name1] [name2, optional] [name3, optional] [name4, optional] [name5, optional]`... (many can be added) or `!vs [apm] [pps] [vs]`\n"
+ "**Extra Parameters:** `-v`: To be added at the end of the command. (Ex: `!vs explorat0ri gavorox -v`). Will display a version of the graph without fill on the colors, aiding visibility with large numbers of players."
+ "\n(*This command supports the use of average players. To use, simply type `$avg[rank]` where a player would be entered.*)"
}
if (name[0] == "vst" || name[0] == "!vst") {
helpContent = "Same thing as `!vs`, but it displays everything in a table.\n"
+ "**Usage** - `!vst [name1] [name2, optional] [name3, optional]`... (many can be added) or `!vst [apm] [pps] [vs]`\n"
+ "\n(*This command supports the use of average players. To use, simply type `$avg[rank]` where a player would be entered.*)"
}
if (name[0] == "sq" || name[0] == "!sq") {
helpContent = "`!sq` - Displays stats of users in a small, 4-axis radar graph.\n"
+ "**Usage** - `!sq [name] [name2, optional] [name3, optional]`... (many can be added) or `!sq [apm] [pps] [vs]`\n"
+ "**Extra Parameters:** `-v`: To be added at the end of the command. (Ex: `!sq explorat0ri -v`). Will display a version of the graph without fill on the colors, aiding visibility with large numbers of players."
+ "\n(*This command supports the use of average players. To use, simply type `$avg[rank]` where a player would be entered.*)"
}
if (name[0] == "psq" || name[0] == "!psq") {
helpContent = "`!psq` - Same thing as sq, but instead of each end being ATTACK, SPEED, DEFENSE and CHEESE, you have OPENER, STRIDE, PLONK and INF DS.\n"
+ "**Usage** - `!psq [name] [name2, optional] [name3, optional]`... (many can be added) or `!psq [apm] [pps] [vs]`.\n"
+ "**Extra Parameters:** `-v`, `-s`. Both are to be added at the end of the command, though only one may be applied at a time.\n"
+ "`-v`: Will display a version of the graph without fill on the colors, aiding visibility with large numbers of players.\n"
+ "`-s`: Will display a scatter plot instead of the traditional radar graph."
+ "\n(*This command supports the use of average players. To use, simply type `$avg[rank]` where a player would be entered.*)"
}
if (name[0] == "ac" || name[0] == "!ac") {
helpContent = "`!ac` - Compares your stats to every other player and finds the closest person to each, individually.\n"
+ "**Usage** - `!ac [name or [apm] [pps] [vs]] [rank or position to start search or \"all\" for all ranks, optional] [rank or position to end search, optional]`\n"
+ "**Examples:** `!ac explorat0ri 400 4000`, `!ac explorat0ri x u`, `!ac explorat0ri all`"
+ "\n(*This command supports the use of average players. To use, simply type `$avg[rank]` where a player would be entered.*)"
+ "\n(*This command uses unranked players. They will show when setting your first position as 0, including `z` in the rank search or if no filters for rank or position are set.*)"
}
if (name[0] == "lb" || name[0] == "!lb") {
helpContent = "`!lb` - Displays a leaderboard of the top players in a certain stat category, based on how many you want to display and search through.\n"
+ "**Usage** - `!lb [any stat name] [how many places to display (ex, 10 for top 10)] [rank or position to start search, optional], [rank or position to stop search, optional], [country using 2-letter area code, \"LATAM\", \"E.U\" or \"null\", optional]`\n"
+ "**Extra Parameters:** `p#` : To be added at the end or before the country parameter if one is included, either works. Acts as a page, changing the number after the p will give a different page. For example, `!lb apm 20` will display #1-#20, while `!lb apm 20 p2` will display #21-39"
+ "\n**Examples:** `!lb apm 10 jp`, `!lb esttr 25 u p2`, `!lb cheese 40 S+ 25000`, `!lb app 20 u 1000 us`"
+ "\n(*This command uses unranked players. They will show when setting your first position as 0, including `z` in the rank search or if no filters for rank or position are set.*)"
}
if (name[0] == "rlb" || name[0] == "!rlb") {
helpContent = "`!rlb` - Same as `!lb` but finds the *bottom* players. Everything else operates the same way."
}
if (name[0] == "cc" || name[0] == "!cc") {
helpContent = "`!cc` - Finds the closest player to you in both rate stats and overall.\n"
+ "**Usage** - `!cc [name or [apm] [pps] [vs]] [display number]`\n"
+ "**Extra Parameters:** `playstyle`, `all`, `rate`, `norate`: All to be placed between the name and display number, though only one should be used at a time. Will make the command display only that respective category."
+ "\n(*This command supports the use of average players. To use, simply type `$avg[rank]` where a player would be entered.*)"
+ "\n(*This command uses unranked players. They will show when setting your first position as 0, including `z` in the rank search or if no filters for rank or position are set.*)"
}
if (name[0] == "avg" || name[0] == "!avg") {
helpContent = "`!avg` - Finds the average stats for a group of players.\n"
+ "**Usage** - `!avg [rank or position to start search or the word \"all\"] [rank or position to end the search, optional] [country using 2-letter area code, \"LATAM\", \"E.U\" or \"null\", optional]`"
}
if (name[0] == "med" || name[0] == "!med") {
helpContent = "`!med` - Finds the median stats for a group of players. Functions identically to !avg in terms of parameters."
}
if (name[0] == "rnk" || name[0] == "!rnk") {
helpContent = "`!rnk` - Determines the placement of your stats among a group of players. You can think of it as showing your placement of each stat on the `!lb` command.\n"
+ "**Usage** - `!rnk [name or [apm] [pps] [vs]] [rank or position to start search at, optional] [rank or position to end search at, optional] [country using 2-letter code, \"LATAM\", \"E.U\" or \"null\", optional]`"
+ "\n(*This command supports the use of average players. To use, simply type `$avg[rank]` where a player would be entered.*)"
}
if (name[0] == "z" || name[0] == "!z") {
helpContent = "`!z` - Handles the list of unranked players.\n"
+ "**Usage** - `!z [list] [the word “id”, optional]` or `!z [add/remove] [username or player ID.]`"
}
if (name[0] == "refresh" || name[0] == "!refresh") {
helpContent = "`!refresh` - Refreshes all of the players for commands like `!lb`, `!avg`, `!ac`, etc. Please do not use this command more than once an hour."
}
if (name[0] == "vsr" || name[0] == "!vsr") {
helpContent = "`!vsr` - Same as `!vs`, but tries to show the values relative to each other. Useful especially for lower ranked players."
}
if (name[0] == "o" || name[0] == "!o") {
helpContent = "`!o` - A command that lists people that fit under a certain criteria. Any criteria supported by `!lb` will be supported here as well.\n"
+ "**Usage** - `!o [stat][<, = or >][number, or rank if stat is rank]`\n"
+ "{These three brackets can be repeated as many times as you want to further narrow down a player.}"
}
let channel = client.channels.cache.get(generalChannelLocal);
await channel.send(helpContent); // Send helpContent
}
function refresh() { // Now using pm2 to restart the bot.
// Run pm2 start sheetBot.js inside directory with sheetBot in it to make it run.
// Using manager.bat can also be useful.
async function firstFunction() {
client.user.setActivity("Restarting...")
return client.channels.cache.get(generalChannel).send("Refreshing... (This may take up to 2 minutes to complete.)")
};
async function secondFunction() {
await firstFunction();
process.exit();
}
secondFunction();
}
async function rankStat(name) {
let generalChannelLocal = generalChannel
var player;
let searchFrom = 0
let searchTo = pList.length
let tempPList = pList.map(a => { return { ...a } }) // Create a copy of the objects in our pList to modify as we see fit
var countrySearch = "default"
var rankSearchTop = ""
var rankSearchBottom = ""
var rankSearchTop;
var rankSearchBottom;
var notInList = false // Variable responsible for showing message if our player isn't in the list we're looking for.
let properties = []
var propertyNames = ["APM", "PPS", "VS", "APP", "DS/Second", "DS/Piece", "APP+DS/Piece", "VS/APM",
"Cheese Index", "Garbage Effi", "Weighted APP", "Area", "Opener", "Plonk", "Stride",
"Inf. DS", "Games", "Wins", "Win Rate", "Est. TR", "TR", "Glicko"]
// Kept here to look nice rather than displaying the raw variable names
// for this function. Making it global could almost certainly be used to save some boilerplate lines of code
// but to be honest, I couldn't be bothered, at least not yet. If you wanted to rearrange some things
// (Namely DS/Second, DS/Piece and APP+DS/Piece), you could probably use it in !vst and !ac as well.
if (name.length > 2) {
if (!isNaN(name[0]) && !isNaN(name[1]) && !isNaN(name[2]) && name[0] > 0 && name[1] > 0 && name[2] > 0) {
player = new Player("EXAMPLE", name[0], name[1], name[2], 0, 0, 60, null)
name.splice(0, 3)
name.unshift("EXAMPLE")
console.log(name)
delete player.games; delete player.wins; delete player.wr; delete player.tr; delete player.glicko;
propertyNames.splice(propertyNames.length - 2, 2)
console.log(propertyNames)
propertyNames.splice(propertyNames.length - 4, 3)
console.log(propertyNames)
}
}
if (name.length > 6) { // Name is too long, too many parameters
client.channels.cache.get(generalChannelLocal).send("Too many arguments.")
return
}
if (name.length < 1 && player == undefined) { // Too few parameters
client.channels.cache.get(generalChannelLocal).send("Too few arguments.")
return
}
if (player == undefined) {
if (name[0] != undefined) { // Set the name string if we have something to set it to
var nameString = name[0].toLowerCase()
if (nameString.length <= 2) { // If our name is too short to be a player name.
return client.channels.cache.get(generalChannelLocal).send(nameString + " is not a valid name!")
}
if (String(name[0].slice(0, 4)).toLowerCase() == "$avg") { // Handles avg players being used
let avgRank = ""
if (rankArray.indexOf(String(name[0].slice(-2).toLowerCase())) == -1) { // If the last two characters don't make something
// that is a rank. You could also use .includes here if you wanted.
avgRank = String(name[0].slice(-1).toLowerCase()) // Grab the first character instead
} else {
avgRank = String(name[0].slice(-2).toLowerCase()) // Otherwise we can assume we had it right the first time and grab the last two
}
player = avgPlayers[rankArray.indexOf(avgRank)] // Makes a deep copy
if (rankArray.indexOf(avgRank) == -1) { // If our average rank still isn't in rank array...
return client.channels.cache.get(generalChannelLocal).send(name[0] + " is an invalid rank!")
}
} else {
await getData("https://ch.tetr.io/api/users/" + nameString + "/summaries/league")
//await axios.get('https://ch.tetr.io/api/users/' + nameString) // Fetch with axios
.then(function (output) { // Then do the following...
console.log(output)
console.log(JSON.stringify(output))
if (output.data.success == false || output == undefined) { // If the player is an anon, the string couldn't be grabbed or leagueCheck is undefined for whatever reason.
return client.channels.cache.get(generalChannelLocal).send(name[0] + " is an invalid user. The user appears to be an anonymous account.")
}
if (output.data.gamesplayed == 0) {
return client.channels.cache.get(generalChannelLocal).send(name[0] + " is an invalid user. Player has never played a Tetra League game.")
}
if (output.data.vs == null) {
return client.channels.cache.get(generalChannelLocal).send(name[0] + "is an invalid user, as they don't have a versus stat!")
}
player = new Player(String(name[0]).toLowerCase(), output.data.apm, output.data.pps, output.data.vs, output.data.tr, output.data.glicko, output.data.rd, output.data)
console.log(player) // Show a console log of them;
})
}
}
if (player == null) { // Some of the axios stuff won't return properly unless we do this.
return
}
}
if ((name[name.length - 1].length == 2 || String(name[name.length - 1]).slice(0, 2).toLowerCase() == "gb") && isNaN(name[name.length - 1]) && name[name.length - 1].slice(-1) != "-" && name[name.length - 1].slice(-1) != "+") {
countrySearch = String(name[name.length - 1]).toUpperCase()
} else {
if (name[name.length - 1] == null || String(name[name.length - 1]).toLowerCase() == "null") {
countrySearch = "null"
}
if (String(name[name.length - 1]).toLowerCase() == "latam") {
countrySearch = "LATAM"
}
if (String(name[name.length - 1]).toLowerCase() == "e.u") {
countrySearch = "E.U"
}
}
// If searchFrom isn't undefined and searchTo is...
if (isNaN(Number(name[2])) && name[2] != undefined && rankArray.indexOf(name[2]) != -1) {
rankSearchBottom = name[2].toLowerCase() // For double rank search when implemented
} else {
if (name[2] != undefined) {
searchTo = Number(name[2])
console.log("searchTo: " + searchTo)
}
}
if (searchTo > pList.length || isNaN(searchTo)) {
searchTo = pList.length
}
console.log(name[1])
if (isNaN(Number(name[1])) && name[1] != undefined) {
rankSearchTop = name[1].toLowerCase() // Determine what rank the player is searching for.
} else {
searchFrom = Number(name[1])
}
if (isNaN(searchFrom)) {
searchFrom = 0
}
if (searchFrom < 0) {
searchFrom = 0
}
console.log("Bottom: " + rankSearchBottom + " , Top: " + rankSearchTop)
console.log("This is searchFrom: " + searchFrom + " and this is searchTo: " + searchTo)
if (rankArray.indexOf(rankSearchTop) != -1 || rankArray.indexOf(rankSearchBottom) != -1) { // If we're searching based on ranks and not positions
console.log("Ranks are being searched!")
if (rankSearchTop != "" && rankSearchTop != -1 && (rankSearchBottom == "" || rankSearchBottom == "")) {
console.log("Set bottom!")
rankSearchBottom = rankSearchTop
}
rankSearchTop = rankArray.indexOf(rankSearchTop)
rankSearchBottom = rankArray.indexOf(rankSearchBottom)
if (rankSearchTop == 17) { // This handles unranked players
searchFrom = 0
}
if (rankSearchTop == 17 && rankSearchBottom != 17) { // So does this
let tmp = rankSearchBottom
rankSearchTop = tmp
rankSearchBottom = 17
}
if (rankSearchBottom == 17) { // And this too
searchFrom = 0
}
tempPList.forEach(function (p) {
p.rank = rankArray.indexOf(p.rank)
});
console.log("Bottom: " + rankSearchBottom + " , Top: " + rankSearchTop)
tempPList = tempPList.filter(p => p.rank <= rankSearchBottom && p.rank >= rankSearchTop && p.position <= searchTo && p.position >= searchFrom); // Filter our copy of the player list to only include people in the rank being
} else {
tempPList = tempPList.filter(p => p.position >= searchFrom && p.position <= searchTo); // Filter our copy of the player list to only include people in the rank being
}
if ((countrySearch.length == 2 && countrySearch != "SS") || countrySearch == "null" || countrySearch.slice(0, 2) == "GB") {
console.log("Hit it!")
tempPList = tempPList.filter(p => p.country == countrySearch)
}
if (countrySearch == "LATAM") {
tempPList = tempPList.filter(p => latamCountries.includes(p.country))
}
if (countrySearch == "E.U") {
tempPList = tempPList.filter(p => euCountries.includes(p.country))
}
if (player.id != "5fe44784036caf0b8fbfc8c5") {
tempPList = tempPList.filter(p => p.id != "5fe44784036caf0b8fbfc8c5") // Gabe louis specifically asked not to be included.
}
if (tempPList.length == 0) {
return client.channels.cache.get(generalChannelLocal).send("There is nobody that matches the specifications you entered!"
+ " This is most likely caused by entering a position or rank combination with no people in it.\n"
+ "For example, please enter `!rnk explorat0ri x u` instead of `!rnk explorat0ri u x`, or `!rnk explorat0ri 100 1000` instead of `!rnk explorat0ri 1000 100`")
}
console.log("countrySearch: " + countrySearch)
for (const property in player) {
if (property != "name" && property != "id" && property != "rank" && property != "estglicko" && property != "esttr" && property != "atr" && property != "avatar" && property != "country" && property != "rd" && property != "srarea" && property != "sr" && property != "tr" && property != "glicko" && property != "position") {
properties.push(property)
}
}
console.log(properties)
properties.push('esttr') // Push these afterwards so they show up at the end of the display together.
if (player.name != "EXAMPLE") {
properties.push('tr')
properties.push('glicko')
}
console.log(properties)
if (tempPList.findIndex(crit => crit.name === player.name) == -1) {
tempPList.push(player)
notInList = true
}
let playerPos = [] // Short for position
let playerPer = [] // Short for percentile
let lbCom = [] // Short for leaderboard command, displays the command you'd have to use to find yourself on the leaderboard
for (let i = 0; i < properties.length; i++) {
tempPList = tempPList.sort((a, b) => { if (a[properties[i]] < b[properties[i]]) return 1; if (a[properties[i]] > b[properties[i]]) return -1; if (a[properties[i]] == b[properties[i]]) return 0; })
playerPos.push(tempPList.findIndex(crit => crit.name === player.name) + 1) // Find the index the player's name is at and make it the position
playerPer.push(Number(((playerPos[i] / tempPList.length) * 100).toFixed(2))) // Percentile is based on position obviously
lbCom.push // Looks really complicated but this is basically to decide whether to display !lb and !rlb and make the command display work
(
(playerPer[i] > 50 && playerPer[i] != 100)
? ('!rlb ' + properties[i] + " 20 " + name.slice(1, name.length).join(" ") + ((name.length > 1) ? " " : "") + ((Math.ceil((tempPList.length - playerPos[i]) / 20) != 1) ? ("p" + Math.ceil((tempPList.length - playerPos[i]) / 20)) : ""))
: ('!lb ' + properties[i] + " 20 " + name.slice(1, name.length).join(" ") + ((name.length > 1) ? " " : "") + ((Math.ceil(playerPos[i] / 20) != 1) ? ("p" + Math.ceil(playerPos[i] / 20) + "") : ""))
)
}
console.log(playerPos)
console.log(playerPer)
console.log(lbCom)
var data = [
[player.name],
[' '],
]
for (let i = 0; i < properties.length; i++) {
data.push([propertyNames[i]])
}
data[0].push("# of " + tempPList.length)
data[0].push("!lb command")
data[1].push(" ")
data[1].push(" ")
for (let i = 2; i < data.length; i++) {
data[i].push(playerPos[i - 2] + " (" + ((playerPer[i - 2] > 50 && playerPer[i - 2] != 100) ? "Bottom " + (100 - playerPer[i - 2]).toFixed(2) + "%)" : "Top " + playerPer[i - 2] + "%)"))
data[i].push(lbCom[i - 2])
// Determine whether to use top or bottom for the % and push the data.
}
const config = {
singleLine: true,
border: getBorderCharacters(`ramac`),
columns: [
{ alignment: 'center' }
],
columnDefault: {
alignment: 'center'
},
drawHorizontalLine: (lineIndex, rowCount) => {
return lineIndex === 0 || lineIndex === 1 || lineIndex === rowCount - 1 || lineIndex === rowCount;
}
}
console.log(table(data, config))
if (notInList == true) {
client.channels.cache.get(generalChannelLocal).send(player.name + " is not in the specified group of players! " +
"Because of this, the table below is a hypothetical- only showing where the player would be if they were in the group.")
}
return client.channels.cache.get(generalChannelLocal).send("```" + table(data, config) + "```");
}
function getAverage(name, median) {
let generalChannelLocal = generalChannel // Just for making sure things get sent to the right channels.
if (name.length > 3) {
return client.channels.cache.get(generalChannelLocal).send("Invalid number of parameters entered.")
// TODO: Add more descriptive text here later.
}
let tempPList = pList.map(a => { return { ...a } }) // Create a copy of the objects in our pList to modify as we see fit
let countryPList;
var countrySearch = name[name.length - 1].toUpperCase();
let searchFrom = 0
let searchTo = pList.length
var rankSearch = false;
var rankSearchTop;
var rankSearchBottom;
var avgPlayer;
var avgCountryPlayer;
if (rankArray.indexOf(countrySearch.toLowerCase()) != -1 || !isNaN(Number(countrySearch)) || countrySearch == "ALL") {
console.log("Set to zz from countrySearch length!") // This does mean that anyone in South Sudan can't be searched for by country but like
countrySearch = "zz" // I really don't see a good fix for this because statements like !avg u ss will always be ambiguous
}
if (countrySearch == "NULL" || countrySearch == "null") {
countrySearch = null
}
console.log(name)
if (isNaN(Number(name[1])) && name[1] != undefined && rankArray.indexOf(name[1]) != -1) {
rankSearchBottom = name[1].toLowerCase() // For double rank search when implemented
} else {
if (name[1] != undefined) {
searchTo = Number(name[1])
console.log("searchTo: " + searchTo)
}
}
if (searchTo > pList.length || isNaN(searchTo)) {
searchTo = pList.length
}
console.log(name[0])
if (isNaN(Number(name[0])) && name[0] != undefined) {
rankSearchTop = name[0].toLowerCase() // Determine what rank the player is searching for.
} else {
searchFrom = Number(name[0])
}
if (isNaN(searchFrom)) {
searchFrom = 0
}
if (searchFrom < 0) {
searchFrom = 0
}
console.log("This is searchFrom: " + searchFrom + " and this is searchTo: " + searchTo)
console.log("This is rankSearchTop: " + rankSearchTop + " and this is rankSearchBottom: " + rankSearchBottom)
console.log(countrySearch)
if (rankArray.indexOf(rankSearchTop) != -1 || rankArray.indexOf(rankSearchBottom) != -1) {
console.log("Ranks are being searched!")
if (rankSearchTop != undefined && rankSearchBottom == undefined) {
rankSearchBottom = rankSearchTop
}
rankSearch = true
rankSearchTop = rankArray.indexOf(rankSearchTop)
rankSearchBottom = rankArray.indexOf(rankSearchBottom)
if (rankSearchTop == 17) { // This handles unranked players
searchFrom = 0
}
if (rankSearchTop == 17 && rankSearchBottom != 17) { // So does this
let tmp = rankSearchBottom
rankSearchTop = tmp
rankSearchBottom = 17
}
if (rankSearchBottom == 17) { // And this too
searchFrom = 0
}
tempPList.forEach(function (p) {
p.rank = rankArray.indexOf(p.rank)
});
tempPList = tempPList.filter(p => p.rank <= rankSearchBottom && p.rank >= rankSearchTop && p.position <= searchTo && p.position >= searchFrom); // Filter our copy of the player list to only include people in the rank being
if (countrySearch != "zz") {
countryPList = tempPList.filter(p => p.country == countrySearch); // Filter our copy of the player list to only include people in the rank being
tempPList = tempPList.filter(p => p.country != countrySearch)
}
} else {
tempPList = tempPList.filter(p => p.position >= searchFrom && p.position <= searchTo); // Filter our copy of the player list to only include people in the rank being
if (countrySearch != "zz") {
countryPList = tempPList.filter(p => p.country == countrySearch);
tempPList = tempPList.filter(p => p.country != countrySearch)
}
if (countrySearch == "LATAM") {
countryPList = tempPList.filter(p => latamCountries.includes(p.country))
}
if (countrySearch == "E.U") {
countryPList = tempPList.filter(p => euCountries.includes(p.country))
}
}
if ((countryPList == undefined || countryPList.length == 0) && countrySearch != "zz") {
return client.channels.cache.get(generalChannelLocal).send("There aren't any people in the country " + countrySearch +
" that meet the parameters set!")