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