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