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