5af9ded7ac945b28d2b75f732f9ecd2ffc5fa25c
[xonotic/xonotic-data.pk3dir.git] / qcsrc / client / hud / panel / scoreboard.qc
1 #include "scoreboard.qh"
2
3 #include "quickmenu.qh"
4 #include <common/ent_cs.qh>
5 #include <common/constants.qh>
6 #include <common/mapinfo.qh>
7 #include <common/minigames/cl_minigames.qh>
8 #include <common/stats.qh>
9 #include <common/teams.qh>
10
11 float sbt_bg_alpha;
12 float sbt_fg_alpha;
13 float sbt_fg_alpha_self;
14 bool sbt_highlight;
15 float sbt_highlight_alpha;
16 float sbt_highlight_alpha_self;
17
18 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
19 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
20 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
21 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0.7;
22 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
23 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
24 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
25 bool autocvar_hud_panel_scoreboard_table_highlight = true;
26 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
27 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.5;
28
29 bool autocvar_hud_panel_scoreboard_accuracy = true;
30 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
31 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
32
33 bool autocvar_hud_panel_scoreboard_dynamichud = false;
34
35
36 void drawstringright(vector, string, vector, vector, float, float);
37 void drawstringcenter(vector, string, vector, vector, float, float);
38
39 // wrapper to put all possible scores titles through gettext
40 string TranslateScoresLabel(string l)
41 {
42         switch(l)
43         {
44                 case "bckills": return CTX(_("SCO^bckills"));
45                 case "bctime": return CTX(_("SCO^bctime"));
46                 case "caps": return CTX(_("SCO^caps"));
47                 case "captime": return CTX(_("SCO^captime"));
48                 case "deaths": return CTX(_("SCO^deaths"));
49                 case "destroyed": return CTX(_("SCO^destroyed"));
50                 case "dmg": return CTX(_("SCO^dmg"));
51                 case "dmgtaken": return CTX(_("SCO^dmgtaken"));
52                 case "drops": return CTX(_("SCO^drops"));
53                 case "faults": return CTX(_("SCO^faults"));
54                 case "fckills": return CTX(_("SCO^fckills"));
55                 case "goals": return CTX(_("SCO^goals"));
56                 case "kckills": return CTX(_("SCO^kckills"));
57                 case "kdratio": return CTX(_("SCO^kdratio"));
58                 case "k/d": return CTX(_("SCO^k/d"));
59                 case "kd": return CTX(_("SCO^kd"));
60                 case "kdr": return CTX(_("SCO^kdr"));
61                 case "kills": return CTX(_("SCO^kills"));
62                 case "laps": return CTX(_("SCO^laps"));
63                 case "lives": return CTX(_("SCO^lives"));
64                 case "losses": return CTX(_("SCO^losses"));
65                 case "name": return CTX(_("SCO^name"));
66                 case "sum": return CTX(_("SCO^sum"));
67                 case "nick": return CTX(_("SCO^nick"));
68                 case "objectives": return CTX(_("SCO^objectives"));
69                 case "pickups": return CTX(_("SCO^pickups"));
70                 case "ping": return CTX(_("SCO^ping"));
71                 case "pl": return CTX(_("SCO^pl"));
72                 case "pushes": return CTX(_("SCO^pushes"));
73                 case "rank": return CTX(_("SCO^rank"));
74                 case "returns": return CTX(_("SCO^returns"));
75                 case "revivals": return CTX(_("SCO^revivals"));
76                 case "score": return CTX(_("SCO^score"));
77                 case "suicides": return CTX(_("SCO^suicides"));
78                 case "takes": return CTX(_("SCO^takes"));
79                 case "ticks": return CTX(_("SCO^ticks"));
80                 default: return l;
81         }
82 }
83
84 void Scoreboard_InitScores()
85 {
86         int i, f;
87
88         ps_primary = ps_secondary = ts_primary = ts_secondary = -1;
89         for(i = 0; i < MAX_SCORE; ++i)
90         {
91                 f = (scores_flags[i] & SFL_SORT_PRIO_MASK);
92                 if(f == SFL_SORT_PRIO_PRIMARY)
93                         ps_primary = i;
94                 if(f == SFL_SORT_PRIO_SECONDARY)
95                         ps_secondary = i;
96         }
97         if(ps_secondary == -1)
98                 ps_secondary = ps_primary;
99
100         for(i = 0; i < MAX_TEAMSCORE; ++i)
101         {
102                 f = (teamscores_flags[i] & SFL_SORT_PRIO_MASK);
103                 if(f == SFL_SORT_PRIO_PRIMARY)
104                         ts_primary = i;
105                 if(f == SFL_SORT_PRIO_SECONDARY)
106                         ts_secondary = i;
107         }
108         if(ts_secondary == -1)
109                 ts_secondary = ts_primary;
110
111         Cmd_Scoreboard_SetFields(0);
112 }
113
114 float SetTeam(entity pl, float Team);
115 //float lastpnum;
116 void Scoreboard_UpdatePlayerTeams()
117 {
118         float Team;
119         entity pl, tmp;
120         float num;
121
122         num = 0;
123         for(pl = players.sort_next; pl; pl = pl.sort_next)
124         {
125                 num += 1;
126                 Team = entcs_GetScoreTeam(pl.sv_entnum);
127                 if(SetTeam(pl, Team))
128                 {
129                         tmp = pl.sort_prev;
130                         Scoreboard_UpdatePlayerPos(pl);
131                         if(tmp)
132                                 pl = tmp;
133                         else
134                                 pl = players.sort_next;
135                 }
136         }
137         /*
138         if(num != lastpnum)
139                 print(strcat("PNUM: ", ftos(num), "\n"));
140         lastpnum = num;
141         */
142 }
143
144 int Scoreboard_CompareScore(int vl, int vr, int f)
145 {
146     TC(int, vl); TC(int, vr); TC(int, f);
147         if(f & SFL_ZERO_IS_WORST)
148         {
149                 if(vl == 0 && vr != 0)
150                         return 1;
151                 if(vl != 0 && vr == 0)
152                         return 0;
153         }
154         if(vl > vr)
155                 return IS_INCREASING(f);
156         if(vl < vr)
157                 return IS_DECREASING(f);
158         return -1;
159 }
160
161 float Scoreboard_ComparePlayerScores(entity left, entity right)
162 {
163         float vl, vr, r;
164         vl = entcs_GetTeam(left.sv_entnum);
165         vr = entcs_GetTeam(right.sv_entnum);
166
167         if(!left.gotscores)
168                 vl = NUM_SPECTATOR;
169         if(!right.gotscores)
170                 vr = NUM_SPECTATOR;
171
172         if(vl > vr)
173                 return true;
174         if(vl < vr)
175                 return false;
176
177         if(vl == NUM_SPECTATOR)
178         {
179                 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
180                 // no other sorting
181                 if(!left.gotscores && right.gotscores)
182                         return true;
183                 return false;
184         }
185
186         r = Scoreboard_CompareScore(left.scores[ps_primary], right.scores[ps_primary], scores_flags[ps_primary]);
187         if (r >= 0)
188                 return r;
189
190         r = Scoreboard_CompareScore(left.scores[ps_secondary], right.scores[ps_secondary], scores_flags[ps_secondary]);
191         if (r >= 0)
192                 return r;
193
194         int i;
195         for(i = 0; i < MAX_SCORE; ++i)
196         {
197                 r = Scoreboard_CompareScore(left.scores[i], right.scores[i], scores_flags[i]);
198                 if (r >= 0)
199                         return r;
200         }
201
202         if (left.sv_entnum < right.sv_entnum)
203                 return true;
204
205         return false;
206 }
207
208 void Scoreboard_UpdatePlayerPos(entity player)
209 {
210         entity ent;
211         for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
212         {
213                 SORT_SWAP(player, ent);
214         }
215         for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
216         {
217                 SORT_SWAP(ent, player);
218         }
219 }
220
221 float Scoreboard_CompareTeamScores(entity left, entity right)
222 {
223         int i, r;
224
225         if(left.team == NUM_SPECTATOR)
226                 return 1;
227         if(right.team == NUM_SPECTATOR)
228                 return 0;
229
230         r = Scoreboard_CompareScore(left.teamscores[ts_primary], right.teamscores[ts_primary], teamscores_flags[ts_primary]);
231         if (r >= 0)
232                 return r;
233
234         r = Scoreboard_CompareScore(left.teamscores[ts_secondary], right.teamscores[ts_secondary], teamscores_flags[ts_secondary]);
235         if (r >= 0)
236                 return r;
237
238         for(i = 0; i < MAX_SCORE; ++i)
239         {
240                 r = Scoreboard_CompareScore(left.teamscores[i], right.teamscores[i], teamscores_flags[i]);
241                 if (r >= 0)
242                         return r;
243         }
244
245         if (left.team < right.team)
246                 return true;
247
248         return false;
249 }
250
251 void Scoreboard_UpdateTeamPos(entity Team)
252 {
253         entity ent;
254         for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
255         {
256                 SORT_SWAP(Team, ent);
257         }
258         for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
259         {
260                 SORT_SWAP(ent, Team);
261         }
262 }
263
264 void Cmd_Scoreboard_Help()
265 {
266         LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"));
267         LOG_INFO(_("^3|---------------------------------------------------------------|\n"));
268         LOG_INFO(_("Usage:\n"));
269         LOG_INFO(_("^2scoreboard_columns_set default\n"));
270         LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
271         LOG_INFO(_("The following field names are recognized (case insensitive):\n"));
272         LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n\n"));
273
274         LOG_INFO(_("^3name^7 or ^3nick^7             Name of a player\n"));
275         LOG_INFO(_("^3ping^7                     Ping time\n"));
276         LOG_INFO(_("^3pl^7                       Packet loss\n"));
277         LOG_INFO(_("^3kills^7                    Number of kills\n"));
278         LOG_INFO(_("^3deaths^7                   Number of deaths\n"));
279         LOG_INFO(_("^3suicides^7                 Number of suicides\n"));
280         LOG_INFO(_("^3frags^7                    kills - suicides\n"));
281         LOG_INFO(_("^3kd^7                       The kill-death ratio\n"));
282         LOG_INFO(_("^3dmg^7                      The total damage done\n"));
283         LOG_INFO(_("^3dmgtaken^7                 The total damage taken\n"));
284         LOG_INFO(_("^3sum^7                      frags - deaths\n"));
285         LOG_INFO(_("^3caps^7                     How often a flag (CTF) or a key (KeyHunt) was captured\n"));
286         LOG_INFO(_("^3pickups^7                  How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
287         LOG_INFO(_("^3captime^7                  Time of fastest cap (CTF)\n"));
288         LOG_INFO(_("^3fckills^7                  Number of flag carrier kills\n"));
289         LOG_INFO(_("^3returns^7                  Number of flag returns\n"));
290         LOG_INFO(_("^3drops^7                    Number of flag drops\n"));
291         LOG_INFO(_("^3lives^7                    Number of lives (LMS)\n"));
292         LOG_INFO(_("^3rank^7                     Player rank\n"));
293         LOG_INFO(_("^3pushes^7                   Number of players pushed into void\n"));
294         LOG_INFO(_("^3destroyed^7                Number of keys destroyed by pushing them into void\n"));
295         LOG_INFO(_("^3kckills^7                  Number of keys carrier kills\n"));
296         LOG_INFO(_("^3losses^7                   Number of times a key was lost\n"));
297         LOG_INFO(_("^3laps^7                     Number of laps finished (race/cts)\n"));
298         LOG_INFO(_("^3time^7                     Total time raced (race/cts)\n"));
299         LOG_INFO(_("^3fastest^7                  Time of fastest lap (race/cts)\n"));
300         LOG_INFO(_("^3ticks^7                    Number of ticks (DOM)\n"));
301         LOG_INFO(_("^3takes^7                    Number of domination points taken (DOM)\n"));
302         LOG_INFO(_("^3bckills^7                  Number of ball carrier kills\n"));
303         LOG_INFO(_("^3bctime^7                   Total amount of time holding the ball in Keepaway\n"));
304         LOG_INFO(_("^3score^7                    Total score\n\n"));
305
306         LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
307                 "of game types, then a slash, to make the field show up only in these\n"
308                 "or in all but these game types. You can also specify 'all' as a\n"
309                 "field to show all fields available for the current game mode.\n\n"));
310
311         LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
312                 "include/exclude ALL teams/noteams game modes.\n\n"));
313
314         LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4\n"));
315         LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
316                 "right of the vertical bar aligned to the right.\n"));
317         LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
318                 "other gamemodes except DM.\n"));
319 }
320
321 // NOTE: adding a gametype with ? to not warn for an optional field
322 // make sure it's excluded in a previous exclusive rule, if any
323 // otherwise the previous exclusive rule warns anyway
324 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
325 #define SCOREBOARD_DEFAULT_COLUMNS \
326 "ping pl name |" \
327 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
328 " -teams,lms/deaths +ft,tdm/deaths" \
329 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
330 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
331 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
332 " +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
333 " +lms/lives +lms/rank" \
334 " +kh/caps +kh/pushes +kh/destroyed" \
335 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
336 " +as/objectives +nb/faults +nb/goals" \
337 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
338 " -lms,rc,cts,inv,nb/score"
339
340 void Cmd_Scoreboard_SetFields(int argc)
341 {
342     TC(int, argc);
343         int i, j, slash;
344         string str, pattern;
345         float have_name = 0, have_primary = 0, have_secondary = 0, have_separator = 0;
346         float missing;
347
348         if(!gametype)
349         {
350                 // set up a temporary scoreboard layout
351                 // no layout can be properly set up until score_info data haven't been received
352                 argc = tokenizebyseparator("0 1 ping pl name | score", " ");
353                 ps_primary = 0;
354                 scores_label[ps_primary] = strzone("score");
355                 scores_flags[ps_primary] = SFL_ALLOW_HIDE;
356         }
357
358         // TODO: re enable with gametype dependant cvars?
359         if(argc < 3) // no arguments provided
360                 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
361
362         if(argc < 3)
363                 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
364
365         if(argc == 3)
366         {
367                 if(argv(2) == "default")
368                         argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
369                 else if(argv(2) == "all")
370                 {
371                         string s;
372                         s = "ping pl name |";
373                         for(i = 0; i < MAX_SCORE; ++i)
374                         {
375                                 if(i != ps_primary)
376                                 if(i != ps_secondary)
377                                 if(scores_label[i] != "")
378                                         s = strcat(s, " ", scores_label[i]);
379                         }
380                         if(ps_secondary != ps_primary)
381                                 s = strcat(s, " ", scores_label[ps_secondary]);
382                         s = strcat(s, " ", scores_label[ps_primary]);
383                         argc = tokenizebyseparator(strcat("0 1 ", s), " ");
384                 }
385         }
386
387
388         sbt_num_fields = 0;
389
390         hud_fontsize = HUD_GetFontsize("hud_fontsize");
391
392         for(i = 1; i < argc - 1; ++i)
393         {
394                 float nocomplain;
395                 str = argv(i+1);
396
397                 nocomplain = false;
398                 if(substring(str, 0, 1) == "?")
399                 {
400                         nocomplain = true;
401                         str = substring(str, 1, strlen(str) - 1);
402                 }
403
404                 slash = strstrofs(str, "/", 0);
405                 if(slash >= 0)
406                 {
407                         pattern = substring(str, 0, slash);
408                         str = substring(str, slash + 1, strlen(str) - (slash + 1));
409
410                         if (!isGametypeInFilter(gametype, teamplay, false, pattern))
411                                 continue;
412                 }
413
414                 strunzone(sbt_field_title[sbt_num_fields]);
415                 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
416                 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
417                 str = strtolower(str);
418
419                 switch(str)
420                 {
421                         case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
422                         case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
423                         case "kd": case "kdr": case "kdratio": case "k/d": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
424                         case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
425                         case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
426                         case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
427                         case "dmg": sbt_field[sbt_num_fields] = SP_DMG; break;
428                         case "dmgtaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
429                         default:
430                         {
431                                 for(j = 0; j < MAX_SCORE; ++j)
432                                         if(str == strtolower(scores_label[j]))
433                                                 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
434
435 LABEL(notfound)
436                                 if(str == "frags")
437                                         j = SP_FRAGS;
438                                 else
439                                 {
440                                         if(!nocomplain)
441                                                 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
442                                         continue;
443                                 }
444 LABEL(found)
445                                 sbt_field[sbt_num_fields] = j;
446                                 if(j == ps_primary)
447                                         have_primary = 1;
448                                 if(j == ps_secondary)
449                                         have_secondary = 1;
450
451                         }
452                 }
453                 ++sbt_num_fields;
454                 if(sbt_num_fields >= MAX_SBT_FIELDS)
455                         break;
456         }
457
458         if(scores_flags[ps_primary] & SFL_ALLOW_HIDE)
459                 have_primary = 1;
460         if(scores_flags[ps_secondary] & SFL_ALLOW_HIDE)
461                 have_secondary = 1;
462         if(ps_primary == ps_secondary)
463                 have_secondary = 1;
464         missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
465
466         if(sbt_num_fields + missing < MAX_SBT_FIELDS)
467         {
468                 if(!have_name)
469                 {
470                         strunzone(sbt_field_title[sbt_num_fields]);
471                         for(i = sbt_num_fields; i > 0; --i)
472                         {
473                                 sbt_field_title[i] = sbt_field_title[i-1];
474                                 sbt_field_size[i] = sbt_field_size[i-1];
475                                 sbt_field[i] = sbt_field[i-1];
476                         }
477                         sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
478                         sbt_field[0] = SP_NAME;
479                         ++sbt_num_fields;
480                         LOG_INFO("fixed missing field 'name'\n");
481
482                         if(!have_separator)
483                         {
484                                 strunzone(sbt_field_title[sbt_num_fields]);
485                                 for(i = sbt_num_fields; i > 1; --i)
486                                 {
487                                         sbt_field_title[i] = sbt_field_title[i-1];
488                                         sbt_field_size[i] = sbt_field_size[i-1];
489                                         sbt_field[i] = sbt_field[i-1];
490                                 }
491                                 sbt_field_title[1] = strzone("|");
492                                 sbt_field[1] = SP_SEPARATOR;
493                                 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
494                                 ++sbt_num_fields;
495                                 LOG_INFO("fixed missing field '|'\n");
496                         }
497                 }
498                 else if(!have_separator)
499                 {
500                         strunzone(sbt_field_title[sbt_num_fields]);
501                         sbt_field_title[sbt_num_fields] = strzone("|");
502                         sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
503                         sbt_field[sbt_num_fields] = SP_SEPARATOR;
504                         ++sbt_num_fields;
505                         LOG_INFO("fixed missing field '|'\n");
506                 }
507                 if(!have_secondary)
508                 {
509                         strunzone(sbt_field_title[sbt_num_fields]);
510                         sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label[ps_secondary]));
511                         sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
512                         sbt_field[sbt_num_fields] = ps_secondary;
513                         ++sbt_num_fields;
514                         LOG_INFOF("fixed missing field '%s'\n", scores_label[ps_secondary]);
515                 }
516                 if(!have_primary)
517                 {
518                         strunzone(sbt_field_title[sbt_num_fields]);
519                         sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label[ps_primary]));
520                         sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
521                         sbt_field[sbt_num_fields] = ps_primary;
522                         ++sbt_num_fields;
523                         LOG_INFOF("fixed missing field '%s'\n", scores_label[ps_primary]);
524                 }
525         }
526
527         sbt_field[sbt_num_fields] = SP_END;
528 }
529
530 // MOVEUP::
531 vector sbt_field_rgb;
532 string sbt_field_icon0;
533 string sbt_field_icon1;
534 string sbt_field_icon2;
535 vector sbt_field_icon0_rgb;
536 vector sbt_field_icon1_rgb;
537 vector sbt_field_icon2_rgb;
538 float sbt_field_icon0_alpha;
539 float sbt_field_icon1_alpha;
540 float sbt_field_icon2_alpha;
541 string Scoreboard_GetField(entity pl, int field)
542 {
543     TC(int, field);
544         float tmp, num, denom;
545         int f;
546         string str;
547         sbt_field_rgb = '1 1 1';
548         sbt_field_icon0 = "";
549         sbt_field_icon1 = "";
550         sbt_field_icon2 = "";
551         sbt_field_icon0_rgb = '1 1 1';
552         sbt_field_icon1_rgb = '1 1 1';
553         sbt_field_icon2_rgb = '1 1 1';
554         sbt_field_icon0_alpha = 1;
555         sbt_field_icon1_alpha = 1;
556         sbt_field_icon2_alpha = 1;
557         switch(field)
558         {
559                 case SP_PING:
560                         if (!pl.gotscores)
561                                 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
562                         //str = getplayerkeyvalue(pl.sv_entnum, "ping");
563                         f = pl.ping;
564                         if(f == 0)
565                                 return _("N/A");
566                         tmp = max(0, min(220, f-80)) / 220;
567                         sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
568                         return ftos(f);
569
570                 case SP_PL:
571                         if (!pl.gotscores)
572                                 return _("N/A");
573                         f = pl.ping_packetloss;
574                         tmp = pl.ping_movementloss;
575                         if(f == 0 && tmp == 0)
576                                 return "";
577                         str = ftos(ceil(f * 100));
578                         if(tmp != 0)
579                                 str = strcat(str, "~", ftos(ceil(tmp * 100)));
580                         tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
581                         sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
582                         return str;
583
584                 case SP_NAME:
585                         if(ready_waiting && pl.ready)
586                         {
587                                 sbt_field_icon0 = "gfx/scoreboard/player_ready";
588                         }
589                         else if(!teamplay)
590                         {
591                                 f = stof(getplayerkeyvalue(pl.sv_entnum, "colors"));
592                                 {
593                                         sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
594                                         sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
595                                         sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
596                                         sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
597                                         sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
598                                 }
599                         }
600                         return entcs_GetName(pl.sv_entnum);
601
602                 case SP_FRAGS:
603                         f = pl.(scores[SP_KILLS]);
604                         f -= pl.(scores[SP_SUICIDES]);
605                         return ftos(f);
606
607                 case SP_KDRATIO:
608                         num = pl.(scores[SP_KILLS]);
609                         denom = pl.(scores[SP_DEATHS]);
610
611                         if(denom == 0) {
612                                 sbt_field_rgb = '0 1 0';
613                                 str = sprintf("%d", num);
614                         } else if(num <= 0) {
615                                 sbt_field_rgb = '1 0 0';
616                                 str = sprintf("%.1f", num/denom);
617                         } else
618                                 str = sprintf("%.1f", num/denom);
619                         return str;
620
621                 case SP_SUM:
622                         f = pl.(scores[SP_KILLS]);
623                         f -= pl.(scores[SP_DEATHS]);
624
625                         if(f > 0) {
626                                 sbt_field_rgb = '0 1 0';
627                         } else if(f == 0) {
628                                 sbt_field_rgb = '1 1 1';
629                         } else {
630                                 sbt_field_rgb = '1 0 0';
631                         }
632                         return ftos(f);
633
634                 case SP_DMG:
635                         num = pl.(scores[SP_DMG]);
636                         denom = 1000;
637
638                         str = sprintf("%.1f k", num/denom);
639                         return str;
640
641                 case SP_DMGTAKEN:
642                         num = pl.(scores[SP_DMGTAKEN]);
643                         denom = 1000;
644
645                         str = sprintf("%.1f k", num/denom);
646                         return str;
647
648                 default:
649                         tmp = pl.(scores[field]);
650                         f = scores_flags[field];
651                         if(field == ps_primary)
652                                 sbt_field_rgb = '1 1 0';
653                         else if(field == ps_secondary)
654                                 sbt_field_rgb = '0 1 1';
655                         else
656                                 sbt_field_rgb = '1 1 1';
657                         return ScoreString(f, tmp);
658         }
659         //return "error";
660 }
661
662 float sbt_fixcolumnwidth_len;
663 float sbt_fixcolumnwidth_iconlen;
664 float sbt_fixcolumnwidth_marginlen;
665
666 string Scoreboard_FixColumnWidth(int i, string str)
667 {
668     TC(int, i);
669         float field, f;
670         vector sz;
671         field = sbt_field[i];
672
673         sbt_fixcolumnwidth_iconlen = 0;
674
675         if(sbt_field_icon0 != "")
676         {
677                 sz = draw_getimagesize(sbt_field_icon0);
678                 f = sz.x / sz.y;
679                 if(sbt_fixcolumnwidth_iconlen < f)
680                         sbt_fixcolumnwidth_iconlen = f;
681         }
682
683         if(sbt_field_icon1 != "")
684         {
685                 sz = draw_getimagesize(sbt_field_icon1);
686                 f = sz.x / sz.y;
687                 if(sbt_fixcolumnwidth_iconlen < f)
688                         sbt_fixcolumnwidth_iconlen = f;
689         }
690
691         if(sbt_field_icon2 != "")
692         {
693                 sz = draw_getimagesize(sbt_field_icon2);
694                 f = sz.x / sz.y;
695                 if(sbt_fixcolumnwidth_iconlen < f)
696                         sbt_fixcolumnwidth_iconlen = f;
697         }
698
699         sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
700
701         if(sbt_fixcolumnwidth_iconlen != 0)
702                 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
703         else
704                 sbt_fixcolumnwidth_marginlen = 0;
705
706         if(field == SP_NAME) // name gets all remaining space
707         {
708                 int j;
709                 float namesize;
710                 namesize = panel_size.x;
711                 for(j = 0; j < sbt_num_fields; ++j)
712                         if(j != i)
713                                 if (sbt_field[i] != SP_SEPARATOR)
714                                         namesize -= sbt_field_size[j] + hud_fontsize.x;
715                 namesize += hud_fontsize.x;
716                 sbt_field_size[i] = namesize;
717
718                 if (sbt_fixcolumnwidth_iconlen != 0)
719                         namesize -= sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen;
720                 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
721                 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
722         }
723         else
724                 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
725
726         f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen;
727         if(sbt_field_size[i] < f)
728                 sbt_field_size[i] = f;
729
730         return str;
731 }
732
733 vector Scoreboard_DrawHeader(vector pos, vector rgb)
734 {
735         int i;
736         vector column_dim = eY * panel_size.y;
737         vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
738         for(i = 0; i < sbt_num_fields; ++i)
739         {
740                 if(sbt_field[i] == SP_SEPARATOR)
741                         break;
742                 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
743                 if (sbt_highlight)
744                         if (i % 2)
745                                 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
746                 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
747                 pos.x += column_dim.x;
748         }
749         if(sbt_field[i] == SP_SEPARATOR)
750         {
751                 pos.x = panel_pos.x + panel_size.x;
752                 for(i = sbt_num_fields - 1; i > 0; --i)
753                 {
754                         if(sbt_field[i] == SP_SEPARATOR)
755                                 break;
756
757                         pos.x -= sbt_field_size[i];
758
759                         if (sbt_highlight)
760                                 if (!(i % 2))
761                                 {
762                                         if (i == sbt_num_fields-1)
763                                                 column_dim.x = sbt_field_size[i] + hud_fontsize.x * 0.5;
764                                         else
765                                                 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
766                                         drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
767                                 }
768
769                         text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
770                         drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
771                         pos.x -= hud_fontsize.x;
772                 }
773         }
774
775         pos.x = panel_pos.x;
776         pos.y += 1.25 * hud_fontsize.y;
777         return pos;
778 }
779
780 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
781 {
782     TC(bool, is_self); TC(int, pl_number);
783         string str;
784         int field;
785         float is_spec;
786         is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
787         if(is_spec && !is_self)
788                 rgb = '0 0 0';
789
790         vector h_pos = item_pos;
791         vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
792         // alternated rows highlighting
793         if(is_self)
794                 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
795         else if((sbt_highlight) && (!(pl_number % 2)))
796                 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
797
798         vector pos = item_pos;
799         pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
800         vector tmp = '0 0 0';
801         int i;
802         for(i = 0; i < sbt_num_fields; ++i)
803         {
804                 field = sbt_field[i];
805                 if(field == SP_SEPARATOR)
806                         break;
807
808                 if(is_spec && field != SP_NAME && field != SP_PING) {
809                         pos.x += sbt_field_size[i] + hud_fontsize.x;
810                         continue;
811                 }
812                 str = Scoreboard_GetField(pl, field);
813                 str = Scoreboard_FixColumnWidth(i, str);
814
815                 pos.x += sbt_field_size[i] + hud_fontsize.x;
816
817                 if(field == SP_NAME) {
818                         tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
819                         if (is_self)
820                                 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
821                         else
822                                 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
823                 } else {
824                         tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
825                         if (is_self)
826                                 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
827                         else
828                                 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
829                 }
830
831                 tmp.x = sbt_field_size[i] + hud_fontsize.x;
832                 if(sbt_field_icon0 != "")
833                         if (is_self)
834                                 drawpic(pos - tmp, sbt_field_icon0, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon0_alpha * sbt_fg_alpha_self, DRAWFLAG_NORMAL);
835                         else
836                                 drawpic(pos - tmp, sbt_field_icon0, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon0_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
837                 if(sbt_field_icon1 != "")
838                         if (is_self)
839                                 drawpic(pos - tmp, sbt_field_icon1, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon1_alpha * sbt_fg_alpha_self, DRAWFLAG_NORMAL);
840                         else
841                                 drawpic(pos - tmp, sbt_field_icon1, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon1_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
842                 if(sbt_field_icon2 != "")
843                         if (is_self)
844                                 drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, sbt_field_icon2_alpha * sbt_fg_alpha_self, DRAWFLAG_NORMAL);
845                         else
846                                 drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, sbt_field_icon2_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
847         }
848
849         if(sbt_field[i] == SP_SEPARATOR)
850         {
851                 pos.x = item_pos.x + panel_size.x;
852                 for(i = sbt_num_fields-1; i > 0; --i)
853                 {
854                         field = sbt_field[i];
855                         if(field == SP_SEPARATOR)
856                                 break;
857
858                         if(is_spec && field != SP_NAME && field != SP_PING) {
859                                 pos.x -= sbt_field_size[i] + hud_fontsize.x;
860                                 continue;
861                         }
862
863                         str = Scoreboard_GetField(pl, field);
864                         str = Scoreboard_FixColumnWidth(i, str);
865
866                         if(field == SP_NAME) {
867                                 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
868                                 if(is_self)
869                                         drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
870                                 else
871                                         drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
872                         } else {
873                                 tmp.x = sbt_fixcolumnwidth_len;
874                                 if(is_self)
875                                         drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
876                                 else
877                                         drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
878                         }
879
880                         tmp.x = sbt_field_size[i];
881                         if(sbt_field_icon0 != "")
882                                 if (is_self)
883                                         drawpic(pos - tmp, sbt_field_icon0, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon0_alpha * sbt_fg_alpha_self, DRAWFLAG_NORMAL);
884                                 else
885                                         drawpic(pos - tmp, sbt_field_icon0, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon0_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
886                         if(sbt_field_icon1 != "")
887                                 if (is_self)
888                                         drawpic(pos - tmp, sbt_field_icon1, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon1_alpha * sbt_fg_alpha_self, DRAWFLAG_NORMAL);
889                                 else
890                                         drawpic(pos - tmp, sbt_field_icon1, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon1_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
891                         if(sbt_field_icon2 != "")
892                                 if (is_self)
893                                         drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, sbt_field_icon2_alpha * sbt_fg_alpha_self, DRAWFLAG_NORMAL);
894                                 else
895                                         drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, sbt_field_icon2_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
896                         pos.x -= sbt_field_size[i] + hud_fontsize.x;
897                 }
898         }
899
900         if(pl.eliminated)
901                 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
902 }
903
904 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
905 {
906         entity pl;
907
908         panel_pos = pos;
909         panel_size.y = 1.25 * hud_fontsize.y * (1 + max(1, tm.team_size));
910         panel_size.y += panel_bg_padding * 2;
911         HUD_Panel_DrawBg(scoreboard_fade_alpha);
912
913         vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
914
915         if(panel_bg_padding)
916         {
917                 panel_pos += '1 1 0' * panel_bg_padding;
918                 panel_size -= '2 2 0' * panel_bg_padding;
919         }
920
921         pos = panel_pos;
922         vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
923
924         // rounded header
925         if (sbt_bg_alpha)
926                 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
927
928         pos.y += 1.25 * hud_fontsize.y;
929
930         // table background
931         tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
932         if (sbt_bg_alpha)
933                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
934
935
936         // print header row and highlight columns
937         pos = Scoreboard_DrawHeader(panel_pos, rgb);
938
939         // fill the table and draw the rows
940         int i = 0;
941         if (teamplay)
942                 for(pl = players.sort_next; pl; pl = pl.sort_next)
943                 {
944                         if(pl.team != tm.team)
945                                 continue;
946                         Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
947                         pos.y += 1.25 * hud_fontsize.y;
948                         ++i;
949                 }
950         else
951                 for(pl = players.sort_next; pl; pl = pl.sort_next)
952                 {
953                         if(pl.team == NUM_SPECTATOR)
954                                 continue;
955                         Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
956                         pos.y += 1.25 * hud_fontsize.y;
957                         ++i;
958                 }
959
960         panel_size.x += panel_bg_padding * 2; // restore initial width
961         return end_pos;
962 }
963
964 float Scoreboard_WouldDraw() {
965         if (QuickMenu_IsOpened())
966                 return 0;
967         else if (HUD_Radar_Clickable())
968                 return 0;
969         else if (scoreboard_showscores)
970                 return 1;
971         else if (intermission == 1)
972                 return 1;
973         else if (intermission == 2)
974                 return 0;
975         else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
976                 return 1;
977         else if (scoreboard_showscores_force)
978                 return 1;
979         return 0;
980 }
981
982 float average_accuracy;
983 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
984 {
985         WepSet weapons_stat = WepSet_GetFromStat();
986         WepSet weapons_inmap = WepSet_GetFromStat_InMap();
987         int disownedcnt = 0;
988         FOREACH(Weapons, it != WEP_Null, {
989                 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
990
991                 WepSet set = it.m_wepset;
992                 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
993                         ++disownedcnt;
994         });
995
996         int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt;
997         if (weapon_cnt <= 0) return pos;
998
999         int rows = 1;
1000         if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - 1) * 0.5))
1001                 rows = 2;
1002         int columnns = ceil(weapon_cnt / rows);
1003
1004         float height = 40;
1005
1006         drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1007         pos.y += 1.25 * hud_fontsize.y;
1008         pos.y += panel_bg_border;
1009
1010         panel_pos = pos;
1011         panel_size.y = height * rows;
1012         panel_size.y += panel_bg_padding * 2;
1013         HUD_Panel_DrawBg(scoreboard_fade_alpha);
1014
1015         vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
1016
1017         if(panel_bg_padding)
1018         {
1019                 panel_pos += '1 1 0' * panel_bg_padding;
1020                 panel_size -= '2 2 0' * panel_bg_padding;
1021         }
1022
1023         pos = panel_pos;
1024         vector tmp = panel_size;
1025
1026         float fontsize = height * 1/3;
1027         float weapon_height = height * 2/3;
1028         float weapon_width = tmp.x / columnns / rows;
1029
1030         if (sbt_bg_alpha)
1031                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1032
1033         if(sbt_highlight)
1034         {
1035                 // column highlighting
1036                 for (int i = 0; i < columnns; ++i)
1037                         if ((i % 2) == 0)
1038                                 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1039
1040                 // row highlighting
1041                 for (int i = 0; i < rows; ++i)
1042                         drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * fontsize, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1043         }
1044
1045         average_accuracy = 0;
1046         int weapons_with_stats = 0;
1047         if (rows == 2)
1048                 pos.x += weapon_width / 2;
1049
1050         if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1051                 rgb = '1 1 1';
1052         else
1053                 Accuracy_LoadColors();
1054
1055         float oldposx = pos.x;
1056         vector tmpos = pos;
1057
1058         int column = 0;
1059         FOREACH(Weapons, it != WEP_Null, {
1060                 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1061
1062                 WepSet set = it.m_wepset;
1063                 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1064                         continue;
1065
1066                 float weapon_alpha;
1067                 if (weapon_stats >= 0)
1068                         weapon_alpha = sbt_fg_alpha;
1069                 else
1070                         weapon_alpha = 0.2 * sbt_fg_alpha;
1071
1072                 // weapon icon
1073                 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1074                 // the accuracy
1075                 if (weapon_stats >= 0) {
1076                         weapons_with_stats += 1;
1077                         average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1078
1079                         string s;
1080                         s = sprintf("%d%%", weapon_stats * 100);
1081
1082                         float padding;
1083                         padding = (weapon_width - stringwidth(s, false, eX * fontsize)) / 2; // center the accuracy value
1084
1085                         if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1086                                 rgb = Accuracy_GetColor(weapon_stats);
1087
1088                         drawstring(tmpos + eX * padding + eY * weapon_height, s, '1 1 0' * fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1089                 }
1090                 tmpos.x += weapon_width * rows;
1091                 pos.x += weapon_width * rows;
1092                 if (rows == 2 && column == columnns - 1) {
1093                         tmpos.x = oldposx;
1094                         tmpos.y += height;
1095                         pos.y += height;
1096                 }
1097                 ++column;
1098         });
1099
1100         if (weapons_with_stats)
1101                 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1102
1103         panel_size.x += panel_bg_padding * 2; // restore initial width
1104         return end_pos;
1105 }
1106
1107 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1108         float px = pos.x;
1109         pos.x += hud_fontsize.x * 0.25;
1110         drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1111         pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1112         drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1113         pos.x = px;
1114         pos.y += hud_fontsize.y;
1115
1116         return pos;
1117 }
1118
1119 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1120         float stat_secrets_found, stat_secrets_total;
1121         float stat_monsters_killed, stat_monsters_total;
1122         float rows = 0;
1123         string val;
1124
1125         // get monster stats
1126         stat_monsters_killed = STAT(MONSTERS_KILLED);
1127         stat_monsters_total = STAT(MONSTERS_TOTAL);
1128         stat_monsters_killed = 14;
1129         stat_monsters_total = 22;
1130
1131         // get secrets stats
1132         stat_secrets_found = STAT(SECRETS_FOUND);
1133         stat_secrets_total = STAT(SECRETS_TOTAL);
1134         stat_secrets_found = 5;
1135         stat_secrets_total = 7;
1136
1137         // get number of rows
1138         if(stat_secrets_total)
1139                 rows += 1;
1140         if(stat_monsters_total)
1141                 rows += 1;
1142
1143         // if no rows, return
1144         if (!rows)
1145                 return pos;
1146
1147         //  draw table header
1148         drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1149         pos.y += 1.25 * hud_fontsize.y;
1150         pos.y += panel_bg_border;
1151
1152         panel_pos = pos;
1153         panel_size.y = hud_fontsize.y * rows;
1154         panel_size.y += panel_bg_padding * 2;
1155         HUD_Panel_DrawBg(scoreboard_fade_alpha);
1156
1157         vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
1158
1159         if(panel_bg_padding)
1160         {
1161                 panel_pos += '1 1 0' * panel_bg_padding;
1162                 panel_size -= '2 2 0' * panel_bg_padding;
1163         }
1164
1165         pos = panel_pos;
1166         vector tmp = panel_size;
1167
1168         if (sbt_bg_alpha)
1169                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1170
1171         // draw monsters
1172         if(stat_monsters_total)
1173         {
1174                 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1175                 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1176         }
1177
1178         // draw secrets
1179         if(stat_secrets_total)
1180         {
1181                 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1182                 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1183         }
1184
1185         panel_size.x += panel_bg_padding * 2; // restore initial width
1186         return end_pos;
1187 }
1188
1189
1190 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1191 {
1192         int i;
1193         RANKINGS_RECEIVED_CNT = 0;
1194         for (i=RANKINGS_CNT-1; i>=0; --i)
1195                 if (grecordtime[i])
1196                         ++RANKINGS_RECEIVED_CNT;
1197
1198         if (RANKINGS_RECEIVED_CNT == 0)
1199                 return pos;
1200
1201         vector hl_rgb = rgb + '0.5 0.5 0.5';
1202
1203         pos.y += hud_fontsize.y;
1204         drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1205         pos.y += 1.25 * hud_fontsize.y;
1206         pos.y += panel_bg_border;
1207
1208         panel_pos = pos;
1209         panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1210         panel_size.y += panel_bg_padding * 2;
1211         HUD_Panel_DrawBg(scoreboard_fade_alpha);
1212
1213         vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
1214
1215         if(panel_bg_padding)
1216         {
1217                 panel_pos += '1 1 0' * panel_bg_padding;
1218                 panel_size -= '2 2 0' * panel_bg_padding;
1219         }
1220
1221         pos = panel_pos;
1222         vector tmp = panel_size;
1223
1224         if (sbt_bg_alpha)
1225                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1226
1227         // row highlighting
1228         for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1229         {
1230                 string n, p;
1231                 float t;
1232                 t = grecordtime[i];
1233                 if (t == 0)
1234                         continue;
1235                 n = grecordholder[i];
1236                 p = count_ordinal(i+1);
1237                 if(grecordholder[i] == entcs_GetName(player_localnum))
1238                         drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1239                 else if(!(i % 2) && sbt_highlight)
1240                         drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1241                 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1242                 drawstring(pos + '3 0 0' * hud_fontsize.y, TIME_ENCODED_TOSTRING(t), '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1243                 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, sbt_fg_alpha, DRAWFLAG_NORMAL);
1244                 pos.y += 1.25 * hud_fontsize.y;
1245         }
1246
1247         panel_size.x += panel_bg_padding * 2; // restore initial width
1248         return end_pos;
1249 }
1250
1251 void Scoreboard_Draw()
1252 {
1253         if(!autocvar__hud_configure)
1254         {
1255                 if(scoreboard_active) {
1256                         if(menu_enabled == 1)
1257                                 scoreboard_fade_alpha = 1;
1258                         float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1259                         if (scoreboard_fadeinspeed)
1260                                 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1261                         else
1262                                 scoreboard_fade_alpha = 1;
1263                 }
1264                 else {
1265                         float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1266                         if (scoreboard_fadeoutspeed)
1267                                 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1268                         else
1269                                 scoreboard_fade_alpha = 0;
1270                 }
1271
1272                 if (!scoreboard_fade_alpha)
1273                         return;
1274         }
1275         else
1276                 scoreboard_fade_alpha = 0;
1277
1278         if (autocvar_hud_panel_scoreboard_dynamichud)
1279                 HUD_Scale_Enable();
1280         else
1281                 HUD_Scale_Disable();
1282
1283         float hud_fade_alpha_save = hud_fade_alpha;
1284         if(menu_enabled == 1)
1285                 hud_fade_alpha = 1;
1286         else
1287                 hud_fade_alpha = scoreboard_fade_alpha * (1 - autocvar__menu_alpha);
1288         HUD_Panel_UpdateCvars();
1289
1290         sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1291         sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1292         sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1293         sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1294         sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1295         sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1296
1297         hud_fade_alpha = hud_fade_alpha_save;
1298
1299         // don't overlap with con_notify
1300         if(!autocvar__hud_configure)
1301                 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1302
1303         Scoreboard_UpdatePlayerTeams();
1304
1305         vector pos, tmp;
1306         entity pl, tm;
1307         string str;
1308
1309         // Initializes position
1310         pos = panel_pos;
1311
1312         // Heading
1313         vector sb_heading_fontsize;
1314         sb_heading_fontsize = hud_fontsize * 2;
1315         draw_beginBoldFont();
1316         drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1317         draw_endBoldFont();
1318
1319         pos.y += sb_heading_fontsize.y;
1320         pos.y += panel_bg_border;
1321
1322         // Draw the scoreboard
1323         float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1324         if(scale <= 0)
1325                 scale = 0.25;
1326         vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1327
1328         if(teamplay)
1329         {
1330                 vector team_score_baseoffset = eY * hud_fontsize.y - eX * (panel_bg_border + hud_fontsize.x * 0.5);
1331                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1332                 {
1333                         if(tm.team == NUM_SPECTATOR)
1334                                 continue;
1335                         if(!tm.team && teamplay)
1336                                 continue;
1337
1338                         draw_beginBoldFont();
1339                         vector rgb = Team_ColorRGB(tm.team);
1340                         str = ftos(tm.(teamscores[ts_primary]));
1341                         drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1342
1343                         if(ts_primary != ts_secondary)
1344                         {
1345                                 str = ftos(tm.(teamscores[ts_secondary]));
1346                                 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize) + eY * hud_fontsize.y * 1.5, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1347                         }
1348                         draw_endBoldFont();
1349
1350                         panel_bg_color = rgb * panel_bg_color_team;
1351                         pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1352                 }
1353                 panel_bg_color = Team_ColorRGB(myteam) * panel_bg_color_team;
1354         }
1355         else
1356         {
1357                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1358                 {
1359                         if(tm.team == NUM_SPECTATOR)
1360                                 continue;
1361                         if(!tm.team && teamplay)
1362                                 continue;
1363
1364                         pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1365                 }
1366         }
1367
1368         if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1369                 if(race_speedaward) {
1370                         drawcolorcodedstring(pos, sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, race_speedaward_unit, race_speedaward_holder), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1371                         pos.y += 1.25 * hud_fontsize.y;
1372                 }
1373                 if(race_speedaward_alltimebest) {
1374                         drawcolorcodedstring(pos, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, race_speedaward_alltimebest_unit, race_speedaward_alltimebest_holder), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1375                         pos.y += 1.25 * hud_fontsize.y;
1376                 }
1377                 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1378         }
1379         else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1380                 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1381
1382         pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1383
1384         // List spectators
1385         float specs = 0;
1386         tmp = pos;
1387         for(pl = players.sort_next; pl; pl = pl.sort_next)
1388         {
1389                 if(pl.team != NUM_SPECTATOR)
1390                         continue;
1391                 pos.y += 1.25 * hud_fontsize.y;
1392                 Scoreboard_DrawItem(pos, panel_bg_color, pl, (pl.sv_entnum == player_localnum), specs);
1393                 ++specs;
1394         }
1395
1396         if(specs)
1397         {
1398                 draw_beginBoldFont();
1399                 drawstring(tmp, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1400                 draw_endBoldFont();
1401                 pos.y += 1.25 * hud_fontsize.y;
1402         }
1403
1404         // Print info string
1405         float tl, fl, ll;
1406         str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1407         tl = STAT(TIMELIMIT);
1408         fl = STAT(FRAGLIMIT);
1409         ll = STAT(LEADLIMIT);
1410         if(gametype == MAPINFO_TYPE_LMS)
1411         {
1412                 if(tl > 0)
1413                         str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1414         }
1415         else
1416         {
1417                 if(tl > 0)
1418                         str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1419                 if(fl > 0)
1420                 {
1421                         if(tl > 0)
1422                                 str = strcat(str, _(" or"));
1423                         if(teamplay)
1424                         {
1425                                 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags[ts_primary], fl),
1426                                         (teamscores_label[ts_primary] == "score")   ? CTX(_("SCO^points")) :
1427                                         (teamscores_label[ts_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1428                                         TranslateScoresLabel(teamscores_label[ts_primary])));
1429                         }
1430                         else
1431                         {
1432                                 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags[ps_primary], fl),
1433                                         (scores_label[ps_primary] == "score")   ? CTX(_("SCO^points")) :
1434                                         (scores_label[ps_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1435                                         TranslateScoresLabel(scores_label[ps_primary])));
1436                         }
1437                 }
1438                 if(ll > 0)
1439                 {
1440                         if(tl > 0 || fl > 0)
1441                                 str = strcat(str, _(" or"));
1442                         if(teamplay)
1443                         {
1444                                 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags[ts_primary], ll),
1445                                         (teamscores_label[ts_primary] == "score")   ? CTX(_("SCO^points")) :
1446                                         (teamscores_label[ts_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1447                                         TranslateScoresLabel(teamscores_label[ts_primary])));
1448                         }
1449                         else
1450                         {
1451                                 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags[ps_primary], ll),
1452                                         (scores_label[ps_primary] == "score")   ? CTX(_("SCO^points")) :
1453                                         (scores_label[ps_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1454                                         TranslateScoresLabel(scores_label[ps_primary])));
1455                         }
1456                 }
1457         }
1458
1459         pos.y += 1.2 * hud_fontsize.y;
1460         drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1461
1462         // print information about respawn status
1463         float respawn_time = STAT(RESPAWN_TIME);
1464         if(!intermission)
1465         if(respawn_time)
1466         {
1467                 if(respawn_time < 0)
1468                 {
1469                         // a negative number means we are awaiting respawn, time value is still the same
1470                         respawn_time *= -1; // remove mark now that we checked it
1471                         respawn_time = max(time, respawn_time); // don't show a negative value while the server is respawning the player (lag)
1472
1473                         str = sprintf(_("^1Respawning in ^3%s^1..."),
1474                                 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1475                                         count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1476                                         :
1477                                         count_seconds(respawn_time - time)
1478                                 )
1479                         );
1480                 }
1481                 else if(time < respawn_time)
1482                 {
1483                         str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1484                                 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1485                                         count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1486                                         :
1487                                         count_seconds(respawn_time - time)
1488                                 )
1489                         );
1490                 }
1491                 else if(time >= respawn_time)
1492                         str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1493
1494                 pos.y += 1.2 * hud_fontsize.y;
1495                 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1496         }
1497
1498         scoreboard_bottom = pos.y + 2 * hud_fontsize.y;
1499 }