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