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