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