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