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