]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/scoreboard.qc
CA: mark eliminated players in the scoreboard
[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/kills -teams,lms/deaths +freezetag/deaths -teams,lms,race,ka/suicides +freezetag/suicides -race,dm,tdm,ka,freezetag/frags ", // tdm already has this in "score"
295                 "+tdm/kills +tdm/deaths +tdm/suicides ",
296                 "+ctf/caps +ctf/pickups +ctf/fckills +ctf/returns ",
297                 "+lms/lives +lms/rank ",
298                 "+kh/caps +kh/pushes +kh/destroyed ",
299                 "?+race/laps ?+race/time ?+race/fastest ",
300                 "+as/objectives +nexball/faults +nexball/goals +ka/pickups +ka/bckills +ka/bctime +freezetag/revivals ",
301                 "-lms,race,nexball/score");
302 }
303
304 void Cmd_HUD_SetFields(float argc)
305 {
306         float i, j, slash;
307         string str, pattern;
308         float have_name = 0, have_primary = 0, have_secondary = 0, have_separator = 0;
309         float missing;
310
311         // TODO: re enable with gametype dependant cvars?
312         if(argc < 3) // no arguments provided
313                 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
314
315         if(argc < 3)
316                 argc = tokenizebyseparator(strcat("0 1 ", HUD_DefaultColumnLayout()), " ");
317
318         if(argc == 3)
319         {
320                 if(argv(2) == "default")
321                         argc = tokenizebyseparator(strcat("0 1 ", HUD_DefaultColumnLayout()), " ");
322                 else if(argv(2) == "all")
323                 {
324                         string s;
325                         s = "ping pl name |";
326                         for(i = 0; i < MAX_SCORE; ++i)
327                         {
328                                 if(i != ps_primary)
329                                 if(i != ps_secondary)
330                                 if(scores_label[i] != "")
331                                         s = strcat(s, " ", scores_label[i]);
332                         }
333                         if(ps_secondary != ps_primary)
334                                 s = strcat(s, " ", scores_label[ps_secondary]);
335                         s = strcat(s, " ", scores_label[ps_primary]);
336                         argc = tokenizebyseparator(strcat("0 1 ", s), " ");
337                 }
338         }
339
340
341         hud_num_fields = 0;
342
343         hud_fontsize = HUD_GetFontsize("hud_fontsize");
344
345         draw_beginBoldFont();
346         for(i = 1; i < argc - 1; ++i)
347         {
348                 float nocomplain;
349                 str = argv(i+1);
350
351                 nocomplain = FALSE;
352                 if(substring(str, 0, 1) == "?")
353                 {
354                         nocomplain = TRUE;
355                         str = substring(str, 1, strlen(str) - 1);
356                 }
357
358                 slash = strstrofs(str, "/", 0);
359                 if(slash >= 0)
360                 {
361                         pattern = substring(str, 0, slash);
362                         str = substring(str, slash + 1, strlen(str) - (slash + 1));
363
364                         if (!isGametypeInFilter(gametype, teamplay, FALSE, pattern))
365                                 continue;
366                 }
367
368                 strunzone(hud_title[hud_num_fields]);
369                 hud_title[hud_num_fields] = strzone(TranslateScoresLabel(str));
370                 hud_size[hud_num_fields] = stringwidth(hud_title[hud_num_fields], FALSE, hud_fontsize);
371                 str = strtolower(str);
372
373                 if(str == "ping") {
374                         hud_field[hud_num_fields] = SP_PING;
375                 } else if(str == "pl") {
376                         hud_field[hud_num_fields] = SP_PL;
377                 } else if(str == "kd" || str == "kdr" || str == "kdratio" || str == "k/d") {
378                         hud_field[hud_num_fields] = SP_KDRATIO;
379                 } else if(str == "sum" || str == "diff" || str == "k-d") {
380                         hud_field[hud_num_fields] = SP_SUM;
381                 } else if(str == "name" || str == "nick") {
382                         hud_field[hud_num_fields] = SP_NAME;
383                         have_name = 1;
384                 } else if(str == "|") {
385                         hud_field[hud_num_fields] = SP_SEPARATOR;
386                         have_separator = 1;
387                 } else {
388                         for(j = 0; j < MAX_SCORE; ++j)
389                                 if(str == strtolower(scores_label[j]))
390                                         goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
391 :notfound
392                         if(str == "frags")
393                         {
394                                 j = SP_FRAGS;
395                         }
396                         else
397                         {
398                                 if (!nocomplain)
399                                         printf("^1Error:^7 Unknown score field: '%s'\n", str);
400                                 continue;
401                         }
402 :found
403                         hud_field[hud_num_fields] = j;
404                         if(j == ps_primary)
405                                 have_primary = 1;
406                         if(j == ps_secondary)
407                                 have_secondary = 1;
408                 }
409                 ++hud_num_fields;
410                 if(hud_num_fields >= MAX_HUD_FIELDS)
411                         break;
412         }
413
414         if(scores_flags[ps_primary] & SFL_ALLOW_HIDE)
415                 have_primary = 1;
416         if(scores_flags[ps_secondary] & SFL_ALLOW_HIDE)
417                 have_secondary = 1;
418         if(ps_primary == ps_secondary)
419                 have_secondary = 1;
420         missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
421
422         if(hud_num_fields+missing < MAX_HUD_FIELDS)
423         {
424                 if(!have_name)
425                 {
426                         strunzone(hud_title[hud_num_fields]);
427                         for(i = hud_num_fields; i > 0; --i)
428                         {
429                                 hud_title[i] = hud_title[i-1];
430                                 hud_size[i] = hud_size[i-1];
431                                 hud_field[i] = hud_field[i-1];
432                         }
433                         hud_title[0] = strzone(TranslateScoresLabel("name"));
434                         hud_field[0] = SP_NAME;
435                         ++hud_num_fields;
436                         print("fixed missing field 'name'\n");
437
438                         if(!have_separator)
439                         {
440                                 strunzone(hud_title[hud_num_fields]);
441                                 for(i = hud_num_fields; i > 1; --i)
442                                 {
443                                         hud_title[i] = hud_title[i-1];
444                                         hud_size[i] = hud_size[i-1];
445                                         hud_field[i] = hud_field[i-1];
446                                 }
447                                 hud_title[1] = strzone("|");
448                                 hud_field[1] = SP_SEPARATOR;
449                                 hud_size[1] = stringwidth("|", FALSE, hud_fontsize);
450                                 ++hud_num_fields;
451                                 print("fixed missing field '|'\n");
452                         }
453                 }
454                 else if(!have_separator)
455                 {
456                         strunzone(hud_title[hud_num_fields]);
457                         hud_title[hud_num_fields] = strzone("|");
458                         hud_size[hud_num_fields] = stringwidth("|", FALSE, hud_fontsize);
459                         hud_field[hud_num_fields] = SP_SEPARATOR;
460                         ++hud_num_fields;
461                         print("fixed missing field '|'\n");
462                 }
463                 if(!have_secondary)
464                 {
465                         strunzone(hud_title[hud_num_fields]);
466                         hud_title[hud_num_fields] = strzone(TranslateScoresLabel(scores_label[ps_secondary]));
467                         hud_size[hud_num_fields] = stringwidth(hud_title[hud_num_fields], FALSE, hud_fontsize);
468                         hud_field[hud_num_fields] = ps_secondary;
469                         ++hud_num_fields;
470                         printf("fixed missing field '%s'\n", scores_label[ps_secondary]);
471                 }
472                 if(!have_primary)
473                 {
474                         strunzone(hud_title[hud_num_fields]);
475                         hud_title[hud_num_fields] = strzone(TranslateScoresLabel(scores_label[ps_primary]));
476                         hud_size[hud_num_fields] = stringwidth(hud_title[hud_num_fields], FALSE, hud_fontsize);
477                         hud_field[hud_num_fields] = ps_primary;
478                         ++hud_num_fields;
479                         printf("fixed missing field '%s'\n", scores_label[ps_primary]);
480                 }
481         }
482
483         hud_field[hud_num_fields] = SP_END;
484         draw_endBoldFont();
485 }
486
487 // MOVEUP::
488 vector hud_field_rgb;
489 string hud_field_icon0;
490 string hud_field_icon1;
491 string hud_field_icon2;
492 vector hud_field_icon0_rgb;
493 vector hud_field_icon1_rgb;
494 vector hud_field_icon2_rgb;
495 float hud_field_icon0_alpha;
496 float hud_field_icon1_alpha;
497 float hud_field_icon2_alpha;
498 string HUD_GetField(entity pl, float field)
499 {
500         float tmp, num, denom, f;
501         string str;
502         hud_field_rgb = '1 1 1';
503         hud_field_icon0 = "";
504         hud_field_icon1 = "";
505         hud_field_icon2 = "";
506         hud_field_icon0_rgb = '1 1 1';
507         hud_field_icon1_rgb = '1 1 1';
508         hud_field_icon2_rgb = '1 1 1';
509         hud_field_icon0_alpha = 1;
510         hud_field_icon1_alpha = 1;
511         hud_field_icon2_alpha = 1;
512         switch(field)
513         {
514                 case SP_PING:
515                         if (!pl.gotscores)
516                                 return "\xEE\x82\x8D\xEE\x82\x8D\xEE\x82\x8D"; // >>> sign
517                         //str = getplayerkeyvalue(pl.sv_entnum, "ping");
518                         f = pl.ping;
519                         if(f == 0)
520                                 return _("N/A");
521                         tmp = max(0, min(220, f-80)) / 220;
522                         hud_field_rgb = '1 1 1' - '0 1 1'*tmp;
523                         return ftos(f);
524
525                 case SP_PL:
526                         if (!pl.gotscores)
527                                 return _("N/A");
528                         f = pl.ping_packetloss;
529                         tmp = pl.ping_movementloss;
530                         if(f == 0 && tmp == 0)
531                                 return "";
532                         str = ftos(ceil(f * 100));
533                         if(tmp != 0)
534                                 str = strcat(str, "~", ftos(ceil(tmp * 100)));
535                         tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
536                         hud_field_rgb = '1 0.5 0.5' - '0 0.5 0.5'*tmp;
537                         return str;
538
539                 case SP_NAME:
540                         if(ready_waiting && pl.ready)
541                         {
542                                 hud_field_icon0 = "gfx/scoreboard/player_ready";
543                         }
544                         if(pl.eliminated)
545                         {
546                                 hud_field_icon0 = "gfx/scoreboard/player_ready"; // TODO: different icon
547                         }
548                         else if(!teamplay)
549                         {
550                                 f = stof(getplayerkeyvalue(pl.sv_entnum, "colors"));
551                                 {
552                                         hud_field_icon0 = "gfx/scoreboard/playercolor_base";
553                                         hud_field_icon1 = "gfx/scoreboard/playercolor_shirt";
554                                         hud_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
555                                         hud_field_icon2 = "gfx/scoreboard/playercolor_pants";
556                                         hud_field_icon2_rgb = colormapPaletteColor(mod(f, 16), 1);
557                                 }
558                         }
559                         return GetPlayerName(pl.sv_entnum);
560
561                 case SP_FRAGS:
562                         f = pl.(scores[SP_KILLS]);
563                         f -= pl.(scores[SP_SUICIDES]);
564                         return ftos(f);
565
566                 case SP_KDRATIO:
567                         num = pl.(scores[SP_KILLS]);
568                         denom = pl.(scores[SP_DEATHS]);
569
570                         if(denom == 0) {
571                                 hud_field_rgb = '0 1 0';
572                                 str = sprintf("%d", num);
573                         } else if(num <= 0) {
574                                 hud_field_rgb = '1 0 0';
575                                 str = sprintf("%.1f", num/denom);
576                         } else
577                                 str = sprintf("%.1f", num/denom);
578                         return str;
579
580                 case SP_SUM:
581                         f = pl.(scores[SP_KILLS]);
582                         f -= pl.(scores[SP_DEATHS]);
583
584                         if(f > 0) {
585                                 hud_field_rgb = '0 1 0';
586                         } else if(f == 0) {
587                                 hud_field_rgb = '1 1 1';
588                         } else {
589                                 hud_field_rgb = '1 0 0';
590                         }
591                         return ftos(f);
592
593                 default:
594                         tmp = pl.(scores[field]);
595                         f = scores_flags[field];
596                         if(field == ps_primary)
597                                 hud_field_rgb = '1 1 0';
598                         else if(field == ps_secondary)
599                                 hud_field_rgb = '0 1 1';
600                         else
601                                 hud_field_rgb = '1 1 1';
602                         return ScoreString(f, tmp);
603         }
604         //return "error";
605 }
606
607 float xmin, xmax, ymin, ymax, sbwidth;
608 float hud_fixscoreboardcolumnwidth_len;
609 float hud_fixscoreboardcolumnwidth_iconlen;
610 float hud_fixscoreboardcolumnwidth_marginlen;
611
612 string HUD_FixScoreboardColumnWidth(float i, string str)
613 {
614         float field, f;
615         vector sz;
616         field = hud_field[i];
617
618         hud_fixscoreboardcolumnwidth_iconlen = 0;
619
620         if(hud_field_icon0 != "")
621         {
622                 sz = draw_getimagesize(hud_field_icon0);
623                 f = sz_x / sz_y;
624                 if(hud_fixscoreboardcolumnwidth_iconlen < f)
625                         hud_fixscoreboardcolumnwidth_iconlen = f;
626         }
627
628         if(hud_field_icon1 != "")
629         {
630                 sz = draw_getimagesize(hud_field_icon1);
631                 f = sz_x / sz_y;
632                 if(hud_fixscoreboardcolumnwidth_iconlen < f)
633                         hud_fixscoreboardcolumnwidth_iconlen = f;
634         }
635
636         if(hud_field_icon2 != "")
637         {
638                 sz = draw_getimagesize(hud_field_icon2);
639                 f = sz_x / sz_y;
640                 if(hud_fixscoreboardcolumnwidth_iconlen < f)
641                         hud_fixscoreboardcolumnwidth_iconlen = f;
642         }
643
644         hud_fixscoreboardcolumnwidth_iconlen *= hud_fontsize_y / hud_fontsize_x; // fix icon aspect
645
646         if(hud_fixscoreboardcolumnwidth_iconlen != 0)
647                 hud_fixscoreboardcolumnwidth_marginlen = stringwidth(" ", FALSE, hud_fontsize);
648         else
649                 hud_fixscoreboardcolumnwidth_marginlen = 0;
650
651         if(field == SP_NAME) // name gets all remaining space
652         {
653                 float namesize, j;
654                 namesize = sbwidth;// / hud_fontsize_x;
655                 for(j = 0; j < hud_num_fields; ++j)
656                         if(j != i)
657                                 if (hud_field[i] != SP_SEPARATOR)
658                                         namesize -= hud_size[j] + hud_fontsize_x;
659                 namesize += hud_fontsize_x;
660                 hud_size[i] = namesize;
661
662                 if (hud_fixscoreboardcolumnwidth_iconlen != 0)
663                         namesize -= hud_fixscoreboardcolumnwidth_marginlen + hud_fixscoreboardcolumnwidth_iconlen;
664                 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
665                 hud_fixscoreboardcolumnwidth_len = stringwidth(str, TRUE, hud_fontsize);
666         }
667         else
668                 hud_fixscoreboardcolumnwidth_len = stringwidth(str, FALSE, hud_fontsize);
669
670         f = hud_fixscoreboardcolumnwidth_len + hud_fixscoreboardcolumnwidth_marginlen + hud_fixscoreboardcolumnwidth_iconlen;
671         if(hud_size[i] < f)
672                 hud_size[i] = f;
673
674         return str;
675 }
676
677 void HUD_PrintScoreboardItem(vector pos, entity pl, float is_self, float pl_number)
678 {
679         vector tmp, rgb;
680         rgb = Team_ColorRGB(pl.team);
681         string str;
682         float i, field;
683         float is_spec;
684         is_spec = (GetPlayerColor(pl.sv_entnum) == NUM_SPECTATOR);
685
686         if((rgb == '1 1 1') && (!is_spec)) {
687                 rgb_x = autocvar_scoreboard_color_bg_r + 0.5;
688                 rgb_y = autocvar_scoreboard_color_bg_g + 0.5;
689                 rgb_z = autocvar_scoreboard_color_bg_b + 0.5; }
690
691         // Layout:
692         tmp_x = sbwidth;
693         tmp_y = hud_fontsize_y * 1.25;
694         tmp_z = 0;
695
696         // alternated rows highlighting
697         if(is_self)
698                 drawfill(pos - '1 1 0', tmp + '2 0 0', rgb, scoreboard_highlight_alpha_self, DRAWFLAG_NORMAL);
699         else if((scoreboard_highlight) && (!mod(pl_number,2)))
700                 drawfill(pos - '1 1 0', tmp + '2 0 0', rgb, scoreboard_highlight_alpha, DRAWFLAG_NORMAL);
701
702         tmp_y = 0;
703
704         for(i = 0; i < hud_num_fields; ++i)
705         {
706                 field = hud_field[i];
707                 if(field == SP_SEPARATOR)
708                         break;
709
710                 if(is_spec && field != SP_NAME && field != SP_PING) {
711                         pos_x += hud_size[i] + hud_fontsize_x;
712                         continue;
713                 }
714                 str = HUD_GetField(pl, field);
715                 str = HUD_FixScoreboardColumnWidth(i, str);
716
717                 pos_x += hud_size[i] + hud_fontsize_x;
718
719                 if(field == SP_NAME) {
720                         tmp_x = hud_size[i] - hud_fontsize_x*hud_fixscoreboardcolumnwidth_iconlen - hud_fixscoreboardcolumnwidth_marginlen + hud_fontsize_x;
721                         if (is_self)
722                                 drawcolorcodedstring(pos - tmp, str, hud_fontsize, scoreboard_alpha_name_self, DRAWFLAG_NORMAL);
723                         else
724                                 drawcolorcodedstring(pos - tmp, str, hud_fontsize, scoreboard_alpha_name, DRAWFLAG_NORMAL);
725                 } else {
726                         tmp_x = hud_fixscoreboardcolumnwidth_len + hud_fontsize_x;
727                         if (is_self)
728                                 drawstring(pos - tmp, str, hud_fontsize, hud_field_rgb, scoreboard_alpha_name_self, DRAWFLAG_NORMAL);
729                         else
730                                 drawstring(pos - tmp, str, hud_fontsize, hud_field_rgb, scoreboard_alpha_name, DRAWFLAG_NORMAL);
731                 }
732
733                 tmp_x = hud_size[i] + hud_fontsize_x;
734                 if(hud_field_icon0 != "")
735                         if (is_self)
736                                 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);
737                         else
738                                 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);
739                 if(hud_field_icon1 != "")
740                         if (is_self)
741                                 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);
742                         else
743                                 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);
744                 if(hud_field_icon2 != "")
745                         if (is_self)
746                                 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);
747                         else
748                                 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);
749         }
750
751         if(hud_field[i] == SP_SEPARATOR)
752         {
753                 pos_x = xmax;
754                 for(i = hud_num_fields-1; i > 0; --i)
755                 {
756                         field = hud_field[i];
757                         if(field == SP_SEPARATOR)
758                                 break;
759
760                         if(is_spec && field != SP_NAME && field != SP_PING) {
761                                 pos_x -= hud_size[i] + hud_fontsize_x;
762                                 continue;
763                         }
764
765                         str = HUD_GetField(pl, field);
766                         str = HUD_FixScoreboardColumnWidth(i, str);
767
768                         if(field == SP_NAME) {
769                                 tmp_x = hud_fixscoreboardcolumnwidth_len; // left or right aligned? let's put it right...
770                                 if(is_self)
771                                         drawcolorcodedstring(pos - tmp, str, hud_fontsize, scoreboard_alpha_name_self, DRAWFLAG_NORMAL);
772                                 else
773                                         drawcolorcodedstring(pos - tmp, str, hud_fontsize, scoreboard_alpha_name, DRAWFLAG_NORMAL);
774                         } else {
775                                 tmp_x = hud_fixscoreboardcolumnwidth_len;
776                                 if(is_self)
777                                         drawstring(pos - tmp, str, hud_fontsize, hud_field_rgb, scoreboard_alpha_name_self, DRAWFLAG_NORMAL);
778                                 else
779                                         drawstring(pos - tmp, str, hud_fontsize, hud_field_rgb, scoreboard_alpha_name, DRAWFLAG_NORMAL);
780                         }
781
782                         tmp_x = hud_size[i];
783                         if(hud_field_icon0 != "")
784                                 if (is_self)
785                                         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);
786                                 else
787                                         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);
788                         if(hud_field_icon1 != "")
789                                 if (is_self)
790                                         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);
791                                 else
792                                         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);
793                         if(hud_field_icon2 != "")
794                                 if (is_self)
795                                         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);
796                                 else
797                                         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);
798                         pos_x -= hud_size[i] + hud_fontsize_x;
799                 }
800         }
801 }
802
803 /*
804  * HUD_Scoreboard_MakeTable
805  *
806  * Makes a table for a team (for all playing players in DM) and fills it
807  */
808
809 vector HUD_Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
810 {
811         float body_table_height, i;
812         vector tmp = '0 0 0', column_dim = '0 0 0';
813         entity pl;
814
815         body_table_height = 1.25 * hud_fontsize_y * max(1, tm.team_size); // no player? show 1 empty line
816
817         pos_y += autocvar_scoreboard_border_thickness;
818         pos -= '1 1 0';
819
820         tmp_x = sbwidth + 2;
821         tmp_y = 1.25 * hud_fontsize_y;
822
823         // rounded header
824         if (teamplay)
825                 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, (rgb * autocvar_scoreboard_color_bg_team) + '0.5 0.5 0.5', scoreboard_alpha_bg, DRAWFLAG_NORMAL);
826         else
827                 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', scoreboard_alpha_bg, DRAWFLAG_NORMAL);
828
829         // table border
830         tmp_y += autocvar_scoreboard_border_thickness;
831         tmp_y += body_table_height;
832         drawborderlines(autocvar_scoreboard_border_thickness, pos, tmp, '0 0 0', scoreboard_alpha_bg, DRAWFLAG_NORMAL); // more transparency for the scoreboard
833
834         // separator header/table
835         pos_y += 1.25 * hud_fontsize_y;
836         tmp_y = autocvar_scoreboard_border_thickness;
837         drawfill(pos, tmp, '0 0 0', scoreboard_alpha_bg, DRAWFLAG_NORMAL);
838
839         pos_y += autocvar_scoreboard_border_thickness;
840
841         // table background
842         tmp_y = body_table_height;
843         if (teamplay)
844                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb * autocvar_scoreboard_color_bg_team, scoreboard_alpha_bg, DRAWFLAG_NORMAL);
845         else
846                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, scoreboard_alpha_bg, DRAWFLAG_NORMAL);
847
848         // anyway, apply some color
849         //drawfill(pos, tmp + '2 0 0', rgb, 0.1, DRAWFLAG_NORMAL);
850
851         // go back to the top to make alternated columns highlighting and to print the strings
852         pos_y -= 1.25 * hud_fontsize_y;
853         pos_y -= autocvar_scoreboard_border_thickness;
854
855         pos += '1 1 0';
856
857         if (scoreboard_highlight)
858         {
859                 column_dim_y = 1.25 * hud_fontsize_y; // header
860                 column_dim_y += autocvar_scoreboard_border_thickness;
861                 column_dim_y += body_table_height;
862         }
863
864         // print the strings of the columns headers and draw the columns
865         draw_beginBoldFont();
866         for(i = 0; i < hud_num_fields; ++i)
867         {
868                 if(hud_field[i] == SP_SEPARATOR)
869                         break;
870                 column_dim_x = hud_size[i] + hud_fontsize_x;
871                 if (scoreboard_highlight)
872                 {
873                         if (mod(i,2))
874                                 drawfill(pos - '0 1 0' - hud_fontsize_x / 2 * '1 0 0', column_dim, '0 0 0', scoreboard_alpha_bg * 0.2, DRAWFLAG_NORMAL);
875                 }
876                 drawstring(pos, hud_title[i], hud_fontsize, rgb * 1.5, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
877                 pos_x += column_dim_x;
878         }
879         if(hud_field[i] == SP_SEPARATOR)
880         {
881                 pos_x = xmax;
882                 tmp_y = 0;
883                 for(i = hud_num_fields-1; i > 0; --i)
884                 {
885                         if(hud_field[i] == SP_SEPARATOR)
886                                 break;
887
888                         pos_x -= hud_size[i];
889
890                         if (scoreboard_highlight)
891                         {
892                                 if (!mod(i,2))
893                                 {
894                                         if (i == hud_num_fields-1)
895                                                 column_dim_x = hud_size[i] + hud_fontsize_x / 2 + 1;
896                                         else
897                                                 column_dim_x = hud_size[i] + hud_fontsize_x;
898                                         drawfill(pos - '0 1 0' - hud_fontsize_x / 2 * '1 0 0', column_dim, '0 0 0', scoreboard_alpha_bg * 0.2, DRAWFLAG_NORMAL);
899                                 }
900                         }
901
902                         tmp_x = stringwidth(hud_title[i], FALSE, hud_fontsize);
903                         tmp_x = (hud_size[i] - tmp_x);
904                         drawstring(pos + tmp, hud_title[i], hud_fontsize, rgb * 1.5, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
905                         pos_x -= hud_fontsize_x;
906                 }
907         }
908         draw_endBoldFont();
909
910         pos_x = xmin;
911         pos_y += 1.25 * hud_fontsize_y; // skip the header
912         pos_y += autocvar_scoreboard_border_thickness;
913
914         // fill the table and draw the rows
915         i = 0;
916         if (teamplay)
917                 for(pl = players.sort_next; pl; pl = pl.sort_next)
918                 {
919                         if(pl.team != tm.team)
920                                 continue;
921                         HUD_PrintScoreboardItem(pos, pl, (pl.sv_entnum == player_localnum), i);
922                         pos_y += 1.25 * hud_fontsize_y;
923                         ++i;
924                 }
925         else
926                 for(pl = players.sort_next; pl; pl = pl.sort_next)
927                 {
928                         if(pl.team == NUM_SPECTATOR)
929                                 continue;
930                         HUD_PrintScoreboardItem(pos, pl, (pl.sv_entnum == player_localnum), i);
931                         pos_y += 1.25 * hud_fontsize_y;
932                         ++i;
933                 }
934
935         if (i == 0)
936                 pos_y += 1.25 * hud_fontsize_y; // move to the end of the table
937         pos_y += 1.25 * hud_fontsize_y; // move empty row (out of the table)
938
939         return pos;
940 }
941
942 float HUD_WouldDrawScoreboard() {
943         if (autocvar__hud_configure)
944                 return 0;
945         else if (scoreboard_showscores)
946                 return 1;
947         else if (intermission == 1)
948                 return 1;
949         else if (intermission == 2)
950                 return 0;
951         else if (spectatee_status != -1 && getstati(STAT_HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS)
952                 return 1;
953         else if (scoreboard_showscores_force)
954                 return 1;
955         return 0;
956 }
957
958 float average_accuracy;
959 vector HUD_DrawScoreboardAccuracyStats(vector pos, vector rgb, vector bg_size)
960 {
961         float i;
962         float weapon_cnt = WEP_COUNT - 3; // either minstanex/nex are hidden, no port-o-launch, no tuba
963         float rows;
964         if(autocvar_scoreboard_accuracy_doublerows)
965                 rows = 2;
966         else
967                 rows = 1;
968         float height = 40;
969         float fontsize = height * 1/3;
970         float weapon_height = height * 2/3;
971         float weapon_width = sbwidth / weapon_cnt;
972         float g_minstagib = 0;
973
974         drawstring(pos, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
975         pos_y += 1.25 * hud_fontsize_y + autocvar_scoreboard_border_thickness;
976         vector tmp = '0 0 0';
977         tmp_x = sbwidth;
978         tmp_y = height * rows;
979
980         if (teamplay)
981                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb * autocvar_scoreboard_color_bg_team, scoreboard_alpha_bg, DRAWFLAG_NORMAL);
982         else
983                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, scoreboard_alpha_bg, DRAWFLAG_NORMAL);
984         drawborderlines(autocvar_scoreboard_border_thickness, pos, tmp, '0 0 0', scoreboard_alpha_bg * 0.75, DRAWFLAG_NORMAL);
985
986         // column highlighting
987         for(i = 0; i < weapon_cnt/rows; ++i)
988         {
989                 if(!mod(i, 2))
990                         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);
991         }
992
993         // row highlighting
994         for(i = 0; i < rows; ++i)
995         {
996                 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);
997         }
998
999         average_accuracy = 0;
1000         float weapons_with_stats;
1001         weapons_with_stats = 0;
1002         if(rows == 2)
1003                 pos_x += weapon_width / 2;
1004
1005         if(switchweapon == WEP_MINSTANEX)
1006                 g_minstagib = 1; // TODO: real detection for minstagib?
1007
1008         float weapon_stats;
1009         if(autocvar_scoreboard_accuracy_nocolors)
1010                 rgb = '1 1 1';
1011         else
1012                 Accuracy_LoadColors();
1013
1014         for(i = WEP_FIRST; i <= WEP_LAST; ++i)
1015         {
1016                 self = get_weaponinfo(i);
1017                 if (!self.weapon)
1018                         continue;
1019                 if ((i == WEP_NEX && g_minstagib) || i == WEP_PORTO || (i == WEP_MINSTANEX && !g_minstagib) || i == WEP_TUBA) // skip port-o-launch, nex || minstanex and tuba
1020                         continue;
1021                 weapon_stats = weapon_accuracy[i-WEP_FIRST];
1022
1023                 float weapon_alpha;
1024                 if(weapon_stats >= 0)
1025                         weapon_alpha = scoreboard_alpha_fg;
1026                 else
1027                         weapon_alpha = 0.2 * scoreboard_alpha_fg;
1028
1029                 // weapon icon
1030                 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);
1031                 // the accuracy
1032                 if(weapon_stats >= 0) {
1033                         weapons_with_stats += 1;
1034                         average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1035
1036                         string s;
1037                         s = sprintf(_("%d%%"), weapon_stats*100);
1038
1039                         float padding;
1040                         padding = (weapon_width - stringwidth(s, FALSE, '1 0 0' * fontsize)) / 2; // center the accuracy value
1041
1042                         if(!autocvar_scoreboard_accuracy_nocolors)
1043                                 rgb = Accuracy_GetColor(weapon_stats);
1044
1045                         drawstring(pos + '1 0 0' * padding + '0 1 0' * weapon_height, s, '1 1 0' * fontsize, rgb, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1046                 }
1047                 pos_x += weapon_width * rows;
1048                 if(rows == 2 && i == 6) {
1049                         pos_x -= sbwidth;
1050                         pos_y += height;
1051                 }
1052         }
1053
1054         if(weapons_with_stats)
1055                 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1056
1057         if(rows == 2)
1058                 pos_x -= weapon_width / 2;
1059         pos_x -= sbwidth;
1060         pos_y += height;
1061
1062         pos_y +=  1.25 * hud_fontsize_y;
1063         return pos;
1064 }
1065
1066 vector HUD_DrawKeyValue(vector pos, string key, string value) {
1067         float px = pos_x;
1068         pos_x += hud_fontsize_x * 0.25;
1069         drawstring(pos, key, hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1070         pos_x = xmax - stringwidth(value, FALSE, hud_fontsize) - hud_fontsize_x * 0.25;
1071         drawstring(pos, value, hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1072         pos_x = px;
1073         pos_y+= hud_fontsize_y;
1074
1075         return pos;
1076 }
1077
1078 vector HUD_DrawMapStats(vector pos, vector rgb, vector bg_size) {
1079         float stat_secrets_found, stat_secrets_total;
1080         float stat_monsters_killed, stat_monsters_total;
1081         float rows = 0;
1082         string val;
1083         
1084         // get monster stats
1085         stat_monsters_killed = getstatf(STAT_MONSTERS_KILLED);
1086         stat_monsters_total = getstatf(STAT_MONSTERS_TOTAL);
1087
1088         // get secrets stats
1089         stat_secrets_found = getstatf(STAT_SECRETS_FOUND);
1090         stat_secrets_total = getstatf(STAT_SECRETS_TOTAL);
1091
1092         // get number of rows
1093         if(stat_secrets_total)
1094                 rows += 1;
1095         if(stat_monsters_total)
1096                 rows += 1;
1097
1098         // if no rows, return
1099         if (!rows)
1100                 return pos;
1101
1102         //  draw table header
1103         drawstring(pos, _("Map stats:"), hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1104         pos_y += 1.25 * hud_fontsize_y + autocvar_scoreboard_border_thickness;
1105
1106         // draw table
1107         vector tmp = '0 0 0';
1108         tmp_x = sbwidth;
1109         tmp_y = hud_fontsize_y * rows;
1110
1111         if (teamplay)
1112                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb * autocvar_scoreboard_color_bg_team, scoreboard_alpha_bg, DRAWFLAG_NORMAL);
1113         else
1114                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, scoreboard_alpha_bg, DRAWFLAG_NORMAL);
1115         drawborderlines(autocvar_scoreboard_border_thickness, pos, tmp, '0 0 0', scoreboard_alpha_bg * 0.75, DRAWFLAG_NORMAL);
1116
1117         // draw monsters
1118         if(stat_monsters_total)
1119         {
1120                 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1121                 pos = HUD_DrawKeyValue(pos, _("Monsters killed:"), val);
1122         }
1123
1124         // draw secrets
1125         if(stat_secrets_total)
1126         {
1127                 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1128                 pos = HUD_DrawKeyValue(pos, _("Secrets found:"), val);
1129         }
1130
1131         // update position
1132         pos_y += 1.25 * hud_fontsize_y;
1133         return pos;
1134 }
1135
1136
1137 vector HUD_DrawScoreboardRankings(vector pos, entity pl,  vector rgb, vector bg_size)
1138 {
1139         float i;
1140         RANKINGS_RECEIVED_CNT = 0;
1141         for (i=RANKINGS_CNT-1; i>=0; --i)
1142                 if (grecordtime[i])
1143                         ++RANKINGS_RECEIVED_CNT;
1144
1145         if (RANKINGS_RECEIVED_CNT == 0)
1146                 return pos;
1147
1148         float is_spec;
1149         is_spec = (GetPlayerColor(pl.sv_entnum) == NUM_SPECTATOR);
1150         vector hl_rgb;
1151         hl_rgb_x = autocvar_scoreboard_color_bg_r + 0.5;
1152         hl_rgb_y = autocvar_scoreboard_color_bg_g + 0.5;
1153         hl_rgb_z = autocvar_scoreboard_color_bg_b + 0.5;
1154
1155         pos_y += hud_fontsize_y;
1156         drawstring(pos, _("Rankings"), hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1157         pos_y += hud_fontsize_y + autocvar_scoreboard_border_thickness;
1158         vector tmp = '0 0 0';
1159         tmp_x = sbwidth;
1160         tmp_y = 1.25 * hud_fontsize_y * RANKINGS_RECEIVED_CNT;
1161
1162         if (teamplay)
1163                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb * autocvar_scoreboard_color_bg_team, scoreboard_alpha_bg, DRAWFLAG_NORMAL);
1164         else
1165                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, scoreboard_alpha_bg, DRAWFLAG_NORMAL);
1166         drawborderlines(autocvar_scoreboard_border_thickness, pos, tmp, '0 0 0', scoreboard_alpha_bg * 0.75, DRAWFLAG_NORMAL);
1167
1168         // row highlighting
1169         for(i = 0; i<RANKINGS_RECEIVED_CNT; ++i)
1170         {
1171                 string n, p;
1172                 float t;
1173                 t = grecordtime[i];
1174                 if (t == 0)
1175                         continue;
1176                 n = grecordholder[i];
1177                 p = count_ordinal(i+1);
1178                 if(grecordholder[i] == GetPlayerName(player_localnum))
1179                         drawfill(pos, '1 0 0' * sbwidth + '0 1.25 0' * hud_fontsize_y, hl_rgb, scoreboard_highlight_alpha_self, DRAWFLAG_NORMAL);
1180                 else if(!mod(i, 2) && scoreboard_highlight)
1181                         drawfill(pos, '1 0 0' * sbwidth + '0 1.25 0' * hud_fontsize_y, hl_rgb, scoreboard_highlight_alpha, DRAWFLAG_NORMAL);
1182                 drawstring(pos, p, '1 1 0' * hud_fontsize_y, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1183                 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);
1184                 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize_y, n, '1 1 0' * hud_fontsize_y, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1185                 pos_y += 1.25 * hud_fontsize_y;
1186         }
1187         pos_y += autocvar_scoreboard_border_thickness;
1188
1189         return pos;
1190 }
1191
1192 float hud_woulddrawscoreboard_prev;
1193 float hud_woulddrawscoreboard_change; // "time" at which HUD_WouldDrawScoreboard() changed
1194 void HUD_DrawScoreboard()
1195 {
1196         float hud_woulddrawscoreboard;
1197         hud_woulddrawscoreboard = scoreboard_active;
1198         if(hud_woulddrawscoreboard != hud_woulddrawscoreboard_prev) {
1199                 hud_woulddrawscoreboard_change = time;
1200                 hud_woulddrawscoreboard_prev = hud_woulddrawscoreboard;
1201         }
1202
1203         if(hud_woulddrawscoreboard) {
1204                 float scoreboard_fadeinspeed = autocvar_scoreboard_fadeinspeed;
1205                 if (scoreboard_fadeinspeed)
1206                         scoreboard_fade_alpha = bound (0, (time - hud_woulddrawscoreboard_change) * scoreboard_fadeinspeed, 1);
1207                 else
1208                         scoreboard_fade_alpha = 1;
1209         }
1210         else {
1211                 float scoreboard_fadeoutspeed = autocvar_scoreboard_fadeoutspeed;
1212                 if (scoreboard_fadeoutspeed)
1213                         scoreboard_fade_alpha = bound (0, (1/scoreboard_fadeoutspeed - (time - hud_woulddrawscoreboard_change)) * scoreboard_fadeoutspeed, 1);
1214                 else
1215                         scoreboard_fade_alpha = 0;
1216         }
1217
1218         if (!scoreboard_fade_alpha)
1219                 return;
1220
1221         HUD_UpdatePlayerTeams();
1222
1223         scoreboard_alpha_bg = autocvar_scoreboard_alpha_bg * scoreboard_fade_alpha * (1 - autocvar__menu_alpha);
1224         scoreboard_alpha_fg = autocvar_scoreboard_alpha_fg * scoreboard_fade_alpha * (1 - autocvar__menu_alpha);
1225         scoreboard_highlight = autocvar_scoreboard_highlight;
1226         scoreboard_highlight_alpha = autocvar_scoreboard_highlight_alpha * scoreboard_alpha_fg;
1227         scoreboard_highlight_alpha_self = autocvar_scoreboard_highlight_alpha_self * scoreboard_alpha_fg;
1228         scoreboard_alpha_name = autocvar_scoreboard_alpha_name * scoreboard_alpha_fg;
1229         scoreboard_alpha_name_self = autocvar_scoreboard_alpha_name_self * scoreboard_alpha_fg;
1230
1231         vector rgb, pos, tmp;
1232         entity pl, tm;
1233         string str;
1234
1235         xmin = (autocvar_scoreboard_offset_left * vid_conwidth);
1236         ymin = max((autocvar_con_notify * autocvar_con_notifysize), (autocvar_scoreboard_offset_vertical * vid_conwidth));
1237
1238         xmax = ((1 - autocvar_scoreboard_offset_right) * vid_conwidth);
1239         ymax = (vid_conheight - ymin);
1240
1241         sbwidth = xmax - xmin;
1242
1243         // Initializes position
1244         pos_x = xmin;
1245         pos_y = ymin;
1246         pos_z = 0;
1247
1248         // Heading
1249         vector sb_heading_fontsize;
1250         sb_heading_fontsize = hud_fontsize * 2;
1251         draw_beginBoldFont();
1252         drawstring(pos, _("Scoreboard"), sb_heading_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1253         draw_endBoldFont();
1254
1255         pos_y += sb_heading_fontsize_y + hud_fontsize_y * 0.25;
1256
1257         // Draw the scoreboard
1258         vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * autocvar_scoreboard_bg_scale;
1259
1260         if(teamplay)
1261         {
1262                 vector team_score_baseoffset;
1263                 team_score_baseoffset = eY * (2 * autocvar_scoreboard_border_thickness + hud_fontsize_y) - eX * (autocvar_scoreboard_border_thickness + hud_fontsize_x * 0.25);
1264                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1265                 {
1266                         if(tm.team == NUM_SPECTATOR)
1267                                 continue;
1268
1269                         draw_beginBoldFont();
1270                         rgb = Team_ColorRGB(tm.team);
1271                         str = ftos(tm.(teamscores[ts_primary]));
1272                         drawstring(pos + team_score_baseoffset - eX * stringwidth(str, FALSE, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1273
1274                         if(ts_primary != ts_secondary)
1275                         {
1276                                 str = ftos(tm.(teamscores[ts_secondary]));
1277                                 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);
1278                         }
1279                         draw_endBoldFont();
1280
1281                         pos = HUD_Scoreboard_MakeTable(pos, tm, rgb, bg_size);
1282                 }
1283
1284 #ifdef GMQCC
1285                 rgb = '0 0 0';
1286 #endif
1287                 rgb_x = autocvar_scoreboard_color_bg_r;
1288                 rgb_y = autocvar_scoreboard_color_bg_g;
1289                 rgb_z = autocvar_scoreboard_color_bg_b;
1290         }
1291         else
1292         {
1293 #ifdef GMQCC
1294                 rgb = '0 0 0';
1295 #endif
1296                 rgb_x = autocvar_scoreboard_color_bg_r;
1297                 rgb_y = autocvar_scoreboard_color_bg_g;
1298                 rgb_z = autocvar_scoreboard_color_bg_b;
1299
1300                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1301                 {
1302                         if(tm.team == NUM_SPECTATOR)
1303                                 continue;
1304
1305                         pos = HUD_Scoreboard_MakeTable(pos, tm, rgb, bg_size);
1306                 }
1307         }
1308
1309         if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1310                 if(race_speedaward) {
1311                         drawcolorcodedstring(pos, sprintf(_("Speed award: %d ^7(%s^7)"), race_speedaward, race_speedaward_holder), hud_fontsize, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1312                         pos_y += 1.25 * hud_fontsize_y;
1313                 }
1314                 if(race_speedaward_alltimebest) {
1315                         drawcolorcodedstring(pos, sprintf(_("All-time fastest: %d ^7(%s^7)"), race_speedaward_alltimebest, race_speedaward_alltimebest_holder), hud_fontsize, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1316                         pos_y += 1.25 * hud_fontsize_y;
1317                 }
1318                 pos = HUD_DrawScoreboardRankings(pos, playerslots[player_localnum], rgb, bg_size);
1319         }
1320         else if(autocvar_scoreboard_accuracy && spectatee_status == 0 && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL) {
1321                 if(teamplay)
1322                         pos = HUD_DrawScoreboardAccuracyStats(pos, Team_ColorRGB(myteam), bg_size);
1323                 else
1324                         pos = HUD_DrawScoreboardAccuracyStats(pos, rgb, bg_size);
1325         }
1326
1327
1328         if(teamplay)
1329                 pos = HUD_DrawMapStats(pos, Team_ColorRGB(myteam), bg_size);
1330         else
1331                 pos = HUD_DrawMapStats(pos, rgb, bg_size);
1332
1333         // List spectators
1334         float specs;
1335         specs = 0;
1336         tmp = pos;
1337         for(pl = players.sort_next; pl; pl = pl.sort_next)
1338         {
1339                 if(pl.team != NUM_SPECTATOR)
1340                         continue;
1341                 pos_y += 1.25 * hud_fontsize_y;
1342                 HUD_PrintScoreboardItem(pos, pl, (pl.sv_entnum == player_localnum), specs);
1343                 ++specs;
1344         }
1345
1346         if(specs)
1347         {
1348                 draw_beginBoldFont();
1349                 drawstring(tmp, _("Spectators"), hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1350                 draw_endBoldFont();
1351                 pos_y += 1.25 * hud_fontsize_y;
1352         }
1353
1354         // Print info string
1355         float tl, fl, ll;
1356         str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1357         tl = getstatf(STAT_TIMELIMIT);
1358         fl = getstatf(STAT_FRAGLIMIT);
1359         ll = getstatf(STAT_LEADLIMIT);
1360         if(gametype == MAPINFO_TYPE_LMS)
1361         {
1362                 if(tl > 0)
1363                         str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1364         }
1365         else
1366         {
1367                 if(tl > 0)
1368                         str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1369                 if(fl > 0)
1370                 {
1371                         if(tl > 0)
1372                                 str = strcat(str, _(" or"));
1373                         if(teamplay)
1374                         {
1375                                 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags[ts_primary], fl),
1376                                         (teamscores_label[ts_primary] == "score")   ? CTX(_("SCO^points")) :
1377                                         (teamscores_label[ts_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1378                                         TranslateScoresLabel(teamscores_label[ts_primary])));
1379                         }
1380                         else
1381                         {
1382                                 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags[ps_primary], fl),
1383                                         (scores_label[ps_primary] == "score")   ? CTX(_("SCO^points")) :
1384                                         (scores_label[ps_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1385                                         TranslateScoresLabel(scores_label[ps_primary])));
1386                         }
1387                 }
1388                 if(ll > 0)
1389                 {
1390                         if(tl > 0 || fl > 0)
1391                                 str = strcat(str, _(" or"));
1392                         if(teamplay)
1393                         {
1394                                 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags[ts_primary], ll),
1395                                         (teamscores_label[ts_primary] == "score")   ? CTX(_("SCO^points")) :
1396                                         (teamscores_label[ts_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1397                                         TranslateScoresLabel(teamscores_label[ts_primary])));
1398                         }
1399                         else
1400                         {
1401                                 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags[ps_primary], ll),
1402                                         (scores_label[ps_primary] == "score")   ? CTX(_("SCO^points")) :
1403                                         (scores_label[ps_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1404                                         TranslateScoresLabel(scores_label[ps_primary])));
1405                         }
1406                 }
1407         }
1408
1409         pos_y += 1.2 * hud_fontsize_y;
1410         drawcolorcodedstring(pos + '0.5 0 0' * (sbwidth - stringwidth(str, TRUE, hud_fontsize)), str, hud_fontsize, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1411
1412         // print information about respawn status
1413         float respawn_time = getstatf(STAT_RESPAWN_TIME);
1414         if(!intermission)
1415         if(respawn_time)
1416         {
1417                 if(respawn_time < 0)
1418                 {
1419                         // a negative number means we are awaiting respawn, time value is still the same
1420                         respawn_time *= -1; // remove mark now that we checked it
1421                         respawn_time = max(time, respawn_time); // don't show a negative value while the server is respawning the player (lag)
1422
1423                         str = sprintf(_("^1Respawning in ^3%s^1..."),
1424                                 (autocvar_scoreboard_respawntime_decimals ?
1425                                         count_seconds_decs(respawn_time - time, autocvar_scoreboard_respawntime_decimals)
1426                                         :
1427                                         count_seconds(respawn_time - time)
1428                                 )
1429                         );
1430                 }
1431                 else if(time < respawn_time)
1432                 {
1433                         str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1434                                 (autocvar_scoreboard_respawntime_decimals ?
1435                                         count_seconds_decs(respawn_time - time, autocvar_scoreboard_respawntime_decimals)
1436                                         :
1437                                         count_seconds(respawn_time - time)
1438                                 )
1439                         );
1440                 }
1441                 else if(time >= respawn_time)
1442                         str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1443
1444                 pos_y += 1.2 * hud_fontsize_y;
1445                 drawcolorcodedstring(pos + '0.5 0 0' * (sbwidth - stringwidth(str, TRUE, hud_fontsize)), str, hud_fontsize, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1446         }
1447
1448         scoreboard_bottom = pos_y + 2 * hud_fontsize_y;
1449 }