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