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