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