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