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