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