]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/scoreboard.qc
58583a0eabf61d1b970ada6b439b57f199484b04
[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         float 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 float HUD_CompareScore(float vl, float vr, float 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         float 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         float 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         // TODO: re enable with gametype dependant cvars?
308         if(argc < 3) // no arguments provided
309                 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
310
311         if(argc < 3)
312                 argc = tokenizebyseparator(strcat("0 1 ", HUD_DefaultColumnLayout()), " ");
313
314         if(argc == 3)
315         {
316                 if(argv(2) == "default")
317                         argc = tokenizebyseparator(strcat("0 1 ", HUD_DefaultColumnLayout()), " ");
318                 else if(argv(2) == "all")
319                 {
320                         string s;
321                         s = "ping pl name |";
322                         for(i = 0; i < MAX_SCORE; ++i)
323                         {
324                                 if(i != ps_primary)
325                                 if(i != ps_secondary)
326                                 if(scores_label[i] != "")
327                                         s = strcat(s, " ", scores_label[i]);
328                         }
329                         if(ps_secondary != ps_primary)
330                                 s = strcat(s, " ", scores_label[ps_secondary]);
331                         s = strcat(s, " ", scores_label[ps_primary]);
332                         argc = tokenizebyseparator(strcat("0 1 ", s), " ");
333                 }
334         }
335
336
337         hud_num_fields = 0;
338
339         hud_fontsize = HUD_GetFontsize("hud_fontsize");
340
341         draw_beginBoldFont();
342         for(i = 1; i < argc - 1; ++i)
343         {
344                 float nocomplain;
345                 str = argv(i+1);
346
347                 nocomplain = FALSE;
348                 if(substring(str, 0, 1) == "?")
349                 {
350                         nocomplain = TRUE;
351                         str = substring(str, 1, strlen(str) - 1);
352                 }
353
354                 slash = strstrofs(str, "/", 0);
355                 if(slash >= 0)
356                 {
357                         pattern = substring(str, 0, slash);
358                         str = substring(str, slash + 1, strlen(str) - (slash + 1));
359
360                         if (!isGametypeInFilter(gametype, teamplay, FALSE, pattern))
361                                 continue;
362                 }
363
364                 strunzone(hud_title[hud_num_fields]);
365                 hud_title[hud_num_fields] = strzone(TranslateScoresLabel(str));
366                 hud_size[hud_num_fields] = stringwidth(hud_title[hud_num_fields], FALSE, hud_fontsize);
367                 str = strtolower(str);
368
369                 if(str == "ping") {
370                         hud_field[hud_num_fields] = SP_PING;
371                 } else if(str == "pl") {
372                         hud_field[hud_num_fields] = SP_PL;
373                 } else if(str == "kd" || str == "kdr" || str == "kdratio" || str == "k/d") {
374                         hud_field[hud_num_fields] = SP_KDRATIO;
375                 } else if(str == "sum" || str == "diff" || str == "k-d") {
376                         hud_field[hud_num_fields] = SP_SUM;
377                 } else if(str == "name" || str == "nick") {
378                         hud_field[hud_num_fields] = SP_NAME;
379                         have_name = 1;
380                 } else if(str == "|") {
381                         hud_field[hud_num_fields] = SP_SEPARATOR;
382                         have_separator = 1;
383                 } else {
384                         for(j = 0; j < MAX_SCORE; ++j)
385                                 if(str == strtolower(scores_label[j]))
386                                         goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
387 :notfound
388                         if(str == "frags")
389                         {
390                                 j = SP_FRAGS;
391                         }
392                         else
393                         {
394                                 if (!nocomplain)
395                                         printf("^1Error:^7 Unknown score field: '%s'\n", str);
396                                 continue;
397                         }
398 :found
399                         hud_field[hud_num_fields] = j;
400                         if(j == ps_primary)
401                                 have_primary = 1;
402                         if(j == ps_secondary)
403                                 have_secondary = 1;
404                 }
405                 ++hud_num_fields;
406                 if(hud_num_fields >= MAX_HUD_FIELDS)
407                         break;
408         }
409
410         if(scores_flags[ps_primary] & SFL_ALLOW_HIDE)
411                 have_primary = 1;
412         if(scores_flags[ps_secondary] & SFL_ALLOW_HIDE)
413                 have_secondary = 1;
414         if(ps_primary == ps_secondary)
415                 have_secondary = 1;
416         missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
417
418         if(hud_num_fields+missing < MAX_HUD_FIELDS)
419         {
420                 if(!have_name)
421                 {
422                         strunzone(hud_title[hud_num_fields]);
423                         for(i = hud_num_fields; i > 0; --i)
424                         {
425                                 hud_title[i] = hud_title[i-1];
426                                 hud_size[i] = hud_size[i-1];
427                                 hud_field[i] = hud_field[i-1];
428                         }
429                         hud_title[0] = strzone(TranslateScoresLabel("name"));
430                         hud_field[0] = SP_NAME;
431                         ++hud_num_fields;
432                         print("fixed missing field 'name'\n");
433
434                         if(!have_separator)
435                         {
436                                 strunzone(hud_title[hud_num_fields]);
437                                 for(i = hud_num_fields; i > 1; --i)
438                                 {
439                                         hud_title[i] = hud_title[i-1];
440                                         hud_size[i] = hud_size[i-1];
441                                         hud_field[i] = hud_field[i-1];
442                                 }
443                                 hud_title[1] = strzone("|");
444                                 hud_field[1] = SP_SEPARATOR;
445                                 hud_size[1] = stringwidth("|", FALSE, hud_fontsize);
446                                 ++hud_num_fields;
447                                 print("fixed missing field '|'\n");
448                         }
449                 }
450                 else if(!have_separator)
451                 {
452                         strunzone(hud_title[hud_num_fields]);
453                         hud_title[hud_num_fields] = strzone("|");
454                         hud_size[hud_num_fields] = stringwidth("|", FALSE, hud_fontsize);
455                         hud_field[hud_num_fields] = SP_SEPARATOR;
456                         ++hud_num_fields;
457                         print("fixed missing field '|'\n");
458                 }
459                 if(!have_secondary)
460                 {
461                         strunzone(hud_title[hud_num_fields]);
462                         hud_title[hud_num_fields] = strzone(TranslateScoresLabel(scores_label[ps_secondary]));
463                         hud_size[hud_num_fields] = stringwidth(hud_title[hud_num_fields], FALSE, hud_fontsize);
464                         hud_field[hud_num_fields] = ps_secondary;
465                         ++hud_num_fields;
466                         printf("fixed missing field '%s'\n", scores_label[ps_secondary]);
467                 }
468                 if(!have_primary)
469                 {
470                         strunzone(hud_title[hud_num_fields]);
471                         hud_title[hud_num_fields] = strzone(TranslateScoresLabel(scores_label[ps_primary]));
472                         hud_size[hud_num_fields] = stringwidth(hud_title[hud_num_fields], FALSE, hud_fontsize);
473                         hud_field[hud_num_fields] = ps_primary;
474                         ++hud_num_fields;
475                         printf("fixed missing field '%s'\n", scores_label[ps_primary]);
476                 }
477         }
478
479         hud_field[hud_num_fields] = SP_END;
480         draw_endBoldFont();
481 }
482
483 // MOVEUP::
484 vector hud_field_rgb;
485 string hud_field_icon0;
486 string hud_field_icon1;
487 string hud_field_icon2;
488 vector hud_field_icon0_rgb;
489 vector hud_field_icon1_rgb;
490 vector hud_field_icon2_rgb;
491 float hud_field_icon0_alpha;
492 float hud_field_icon1_alpha;
493 float hud_field_icon2_alpha;
494 string HUD_GetField(entity pl, float field)
495 {
496         float tmp, num, denom, f;
497         string str;
498         hud_field_rgb = '1 1 1';
499         hud_field_icon0 = "";
500         hud_field_icon1 = "";
501         hud_field_icon2 = "";
502         hud_field_icon0_rgb = '1 1 1';
503         hud_field_icon1_rgb = '1 1 1';
504         hud_field_icon2_rgb = '1 1 1';
505         hud_field_icon0_alpha = 1;
506         hud_field_icon1_alpha = 1;
507         hud_field_icon2_alpha = 1;
508         switch(field)
509         {
510                 case SP_PING:
511                         if (!pl.gotscores)
512                                 return "\xEE\x82\x8D\xEE\x82\x8D\xEE\x82\x8D"; // >>> sign
513                         //str = getplayerkeyvalue(pl.sv_entnum, "ping");
514                         f = pl.ping;
515                         if(f == 0)
516                                 return _("N/A");
517                         tmp = max(0, min(220, f-80)) / 220;
518                         hud_field_rgb = '1 1 1' - '0 1 1'*tmp;
519                         return ftos(f);
520
521                 case SP_PL:
522                         if (!pl.gotscores)
523                                 return _("N/A");
524                         f = pl.ping_packetloss;
525                         tmp = pl.ping_movementloss;
526                         if(f == 0 && tmp == 0)
527                                 return "";
528                         str = ftos(ceil(f * 100));
529                         if(tmp != 0)
530                                 str = strcat(str, "~", ftos(ceil(tmp * 100)));
531                         tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
532                         hud_field_rgb = '1 0.5 0.5' - '0 0.5 0.5'*tmp;
533                         return str;
534
535                 case SP_NAME:
536                         if(ready_waiting && pl.ready)
537                         {
538                                 hud_field_icon0 = "gfx/scoreboard/player_ready";
539                         }
540                         else if(!teamplay)
541                         {
542                                 f = stof(getplayerkeyvalue(pl.sv_entnum, "colors"));
543                                 {
544                                         hud_field_icon0 = "gfx/scoreboard/playercolor_base";
545                                         hud_field_icon1 = "gfx/scoreboard/playercolor_shirt";
546                                         hud_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
547                                         hud_field_icon2 = "gfx/scoreboard/playercolor_pants";
548                                         hud_field_icon2_rgb = colormapPaletteColor(mod(f, 16), 1);
549                                 }
550                         }
551                         return GetPlayerName(pl.sv_entnum);
552
553                 case SP_FRAGS:
554                         f = pl.(scores[SP_KILLS]);
555                         f -= pl.(scores[SP_SUICIDES]);
556                         return ftos(f);
557
558                 case SP_KDRATIO:
559                         num = pl.(scores[SP_KILLS]);
560                         denom = pl.(scores[SP_DEATHS]);
561
562                         if(denom == 0) {
563                                 hud_field_rgb = '0 1 0';
564                                 str = sprintf("%d", num);
565                         } else if(num <= 0) {
566                                 hud_field_rgb = '1 0 0';
567                                 str = sprintf("%.1f", num/denom);
568                         } else
569                                 str = sprintf("%.1f", num/denom);
570                         return str;
571
572                 case SP_SUM:
573                         f = pl.(scores[SP_KILLS]);
574                         f -= pl.(scores[SP_DEATHS]);
575
576                         if(f > 0) {
577                                 hud_field_rgb = '0 1 0';
578                         } else if(f == 0) {
579                                 hud_field_rgb = '1 1 1';
580                         } else {
581                                 hud_field_rgb = '1 0 0';
582                         }
583                         return ftos(f);
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 = Team_ColorRGB(pl.team);
673         string str;
674         float i, field;
675         float is_spec;
676         is_spec = (GetPlayerColor(pl.sv_entnum) == NUM_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 == NUM_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 average_accuracy;
951 vector HUD_DrawScoreboardAccuracyStats(vector pos, vector rgb, vector bg_size)
952 {
953         float i;
954         float weapon_cnt = WEP_COUNT - 3; // either minstanex/nex are hidden, no port-o-launch, no tuba
955         float rows;
956         if(autocvar_scoreboard_accuracy_doublerows)
957                 rows = 2;
958         else
959                 rows = 1;
960         float height = 40;
961         float fontsize = height * 1/3;
962         float weapon_height = height * 2/3;
963         float weapon_width = sbwidth / weapon_cnt;
964         float g_instagib = 0;
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_instagib = 1; // TODO: real detection for instagib?
999
1000         float weapon_stats;
1001         if(autocvar_scoreboard_accuracy_nocolors)
1002                 rgb = '1 1 1';
1003         else
1004                 Accuracy_LoadColors();
1005
1006         for(i = WEP_FIRST; i <= WEP_LAST; ++i)
1007         {
1008                 self = get_weaponinfo(i);
1009                 if (!self.weapon)
1010                         continue;
1011                 if ((i == WEP_NEX && g_instagib) || i == WEP_PORTO || (i == WEP_MINSTANEX && !g_instagib) || i == WEP_TUBA) // skip port-o-launch, vortex || vaporizer and tuba
1012                         continue;
1013                 weapon_stats = weapon_accuracy[i-WEP_FIRST];
1014
1015                 float weapon_alpha;
1016                 if(weapon_stats >= 0)
1017                         weapon_alpha = scoreboard_alpha_fg;
1018                 else
1019                         weapon_alpha = 0.2 * scoreboard_alpha_fg;
1020
1021                 // weapon icon
1022                 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);
1023                 // the accuracy
1024                 if(weapon_stats >= 0) {
1025                         weapons_with_stats += 1;
1026                         average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1027
1028                         string s;
1029                         s = sprintf(_("%d%%"), weapon_stats*100);
1030
1031                         float padding;
1032                         padding = (weapon_width - stringwidth(s, FALSE, '1 0 0' * fontsize)) / 2; // center the accuracy value
1033
1034                         if(!autocvar_scoreboard_accuracy_nocolors)
1035                                 rgb = Accuracy_GetColor(weapon_stats);
1036
1037                         drawstring(pos + '1 0 0' * padding + '0 1 0' * weapon_height, s, '1 1 0' * fontsize, rgb, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1038                 }
1039                 pos_x += weapon_width * rows;
1040                 if(rows == 2 && i == 6) {
1041                         pos_x -= sbwidth;
1042                         pos_y += height;
1043                 }
1044         }
1045
1046         if(weapons_with_stats)
1047                 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1048
1049         if(rows == 2)
1050                 pos_x -= weapon_width / 2;
1051         pos_x -= sbwidth;
1052         pos_y += height;
1053
1054         pos_y +=  1.25 * hud_fontsize_y;
1055         return pos;
1056 }
1057
1058 vector HUD_DrawKeyValue(vector pos, string key, string value) {
1059         float px = pos_x;
1060         pos_x += hud_fontsize_x * 0.25;
1061         drawstring(pos, key, hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1062         pos_x = xmax - stringwidth(value, FALSE, hud_fontsize) - hud_fontsize_x * 0.25;
1063         drawstring(pos, value, hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1064         pos_x = px;
1065         pos_y+= hud_fontsize_y;
1066
1067         return pos;
1068 }
1069
1070 vector HUD_DrawMapStats(vector pos, vector rgb, vector bg_size) {
1071         float stat_secrets_found, stat_secrets_total;
1072         float stat_monsters_killed, stat_monsters_total;
1073         float rows = 0;
1074         string val;
1075         
1076         // get monster stats
1077         stat_monsters_killed = getstatf(STAT_MONSTERS_KILLED);
1078         stat_monsters_total = getstatf(STAT_MONSTERS_TOTAL);
1079
1080         // get secrets stats
1081         stat_secrets_found = getstatf(STAT_SECRETS_FOUND);
1082         stat_secrets_total = getstatf(STAT_SECRETS_TOTAL);
1083
1084         // get number of rows
1085         if(stat_secrets_total)
1086                 rows += 1;
1087         if(stat_monsters_total)
1088                 rows += 1;
1089
1090         // if no rows, return
1091         if (!rows)
1092                 return pos;
1093
1094         //  draw table header
1095         drawstring(pos, _("Map stats:"), hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1096         pos_y += 1.25 * hud_fontsize_y + autocvar_scoreboard_border_thickness;
1097
1098         // draw table
1099         vector tmp = '0 0 0';
1100         tmp_x = sbwidth;
1101         tmp_y = hud_fontsize_y * rows;
1102
1103         if (teamplay)
1104                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb * autocvar_scoreboard_color_bg_team, scoreboard_alpha_bg, DRAWFLAG_NORMAL);
1105         else
1106                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, scoreboard_alpha_bg, DRAWFLAG_NORMAL);
1107         drawborderlines(autocvar_scoreboard_border_thickness, pos, tmp, '0 0 0', scoreboard_alpha_bg * 0.75, DRAWFLAG_NORMAL);
1108
1109         // draw monsters
1110         if(stat_monsters_total)
1111         {
1112                 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1113                 pos = HUD_DrawKeyValue(pos, _("Monsters killed:"), val);
1114         }
1115
1116         // draw secrets
1117         if(stat_secrets_total)
1118         {
1119                 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1120                 pos = HUD_DrawKeyValue(pos, _("Secrets found:"), val);
1121         }
1122
1123         // update position
1124         pos_y += 1.25 * hud_fontsize_y;
1125         return pos;
1126 }
1127
1128
1129 vector HUD_DrawScoreboardRankings(vector pos, entity pl,  vector rgb, vector bg_size)
1130 {
1131         float i;
1132         RANKINGS_RECEIVED_CNT = 0;
1133         for (i=RANKINGS_CNT-1; i>=0; --i)
1134                 if (grecordtime[i])
1135                         ++RANKINGS_RECEIVED_CNT;
1136
1137         if (RANKINGS_RECEIVED_CNT == 0)
1138                 return pos;
1139
1140         float is_spec;
1141         is_spec = (GetPlayerColor(pl.sv_entnum) == NUM_SPECTATOR);
1142         vector hl_rgb;
1143         hl_rgb_x = autocvar_scoreboard_color_bg_r + 0.5;
1144         hl_rgb_y = autocvar_scoreboard_color_bg_g + 0.5;
1145         hl_rgb_z = autocvar_scoreboard_color_bg_b + 0.5;
1146
1147         pos_y += hud_fontsize_y;
1148         drawstring(pos, _("Rankings"), hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1149         pos_y += hud_fontsize_y + autocvar_scoreboard_border_thickness;
1150         vector tmp = '0 0 0';
1151         tmp_x = sbwidth;
1152         tmp_y = 1.25 * hud_fontsize_y * RANKINGS_RECEIVED_CNT;
1153
1154         if (teamplay)
1155                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb * autocvar_scoreboard_color_bg_team, scoreboard_alpha_bg, DRAWFLAG_NORMAL);
1156         else
1157                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, scoreboard_alpha_bg, DRAWFLAG_NORMAL);
1158         drawborderlines(autocvar_scoreboard_border_thickness, pos, tmp, '0 0 0', scoreboard_alpha_bg * 0.75, DRAWFLAG_NORMAL);
1159
1160         // row highlighting
1161         for(i = 0; i<RANKINGS_RECEIVED_CNT; ++i)
1162         {
1163                 string n, p;
1164                 float t;
1165                 t = grecordtime[i];
1166                 if (t == 0)
1167                         continue;
1168                 n = grecordholder[i];
1169                 p = count_ordinal(i+1);
1170                 if(grecordholder[i] == GetPlayerName(player_localnum))
1171                         drawfill(pos, '1 0 0' * sbwidth + '0 1.25 0' * hud_fontsize_y, hl_rgb, scoreboard_highlight_alpha_self, DRAWFLAG_NORMAL);
1172                 else if(!mod(i, 2) && scoreboard_highlight)
1173                         drawfill(pos, '1 0 0' * sbwidth + '0 1.25 0' * hud_fontsize_y, hl_rgb, scoreboard_highlight_alpha, DRAWFLAG_NORMAL);
1174                 drawstring(pos, p, '1 1 0' * hud_fontsize_y, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1175                 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);
1176                 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize_y, n, '1 1 0' * hud_fontsize_y, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1177                 pos_y += 1.25 * hud_fontsize_y;
1178         }
1179         pos_y += autocvar_scoreboard_border_thickness;
1180
1181         return pos;
1182 }
1183
1184 float hud_woulddrawscoreboard_prev;
1185 float hud_woulddrawscoreboard_change; // "time" at which HUD_WouldDrawScoreboard() changed
1186 void HUD_DrawScoreboard()
1187 {
1188         float hud_woulddrawscoreboard;
1189         hud_woulddrawscoreboard = scoreboard_active;
1190         if(hud_woulddrawscoreboard != hud_woulddrawscoreboard_prev) {
1191                 hud_woulddrawscoreboard_change = time;
1192                 hud_woulddrawscoreboard_prev = hud_woulddrawscoreboard;
1193         }
1194
1195         if(hud_woulddrawscoreboard) {
1196                 float scoreboard_fadeinspeed = autocvar_scoreboard_fadeinspeed;
1197                 if (scoreboard_fadeinspeed)
1198                         scoreboard_fade_alpha = bound (0, (time - hud_woulddrawscoreboard_change) * scoreboard_fadeinspeed, 1);
1199                 else
1200                         scoreboard_fade_alpha = 1;
1201         }
1202         else {
1203                 float scoreboard_fadeoutspeed = autocvar_scoreboard_fadeoutspeed;
1204                 if (scoreboard_fadeoutspeed)
1205                         scoreboard_fade_alpha = bound (0, (1/scoreboard_fadeoutspeed - (time - hud_woulddrawscoreboard_change)) * scoreboard_fadeoutspeed, 1);
1206                 else
1207                         scoreboard_fade_alpha = 0;
1208         }
1209
1210         if (!scoreboard_fade_alpha)
1211                 return;
1212
1213         HUD_UpdatePlayerTeams();
1214
1215         scoreboard_alpha_bg = autocvar_scoreboard_alpha_bg * scoreboard_fade_alpha * (1 - autocvar__menu_alpha);
1216         scoreboard_alpha_fg = autocvar_scoreboard_alpha_fg * scoreboard_fade_alpha * (1 - autocvar__menu_alpha);
1217         scoreboard_highlight = autocvar_scoreboard_highlight;
1218         scoreboard_highlight_alpha = autocvar_scoreboard_highlight_alpha * scoreboard_alpha_fg;
1219         scoreboard_highlight_alpha_self = autocvar_scoreboard_highlight_alpha_self * scoreboard_alpha_fg;
1220         scoreboard_alpha_name = autocvar_scoreboard_alpha_name * scoreboard_alpha_fg;
1221         scoreboard_alpha_name_self = autocvar_scoreboard_alpha_name_self * scoreboard_alpha_fg;
1222
1223         vector rgb, pos, tmp;
1224         entity pl, tm;
1225         string str;
1226
1227         xmin = (autocvar_scoreboard_offset_left * vid_conwidth);
1228         ymin = max((autocvar_con_notify * autocvar_con_notifysize), (autocvar_scoreboard_offset_vertical * vid_conwidth));
1229
1230         xmax = ((1 - autocvar_scoreboard_offset_right) * vid_conwidth);
1231         ymax = (vid_conheight - ymin);
1232
1233         sbwidth = xmax - xmin;
1234
1235         // Initializes position
1236         pos_x = xmin;
1237         pos_y = ymin;
1238         pos_z = 0;
1239
1240         // Heading
1241         vector sb_heading_fontsize;
1242         sb_heading_fontsize = hud_fontsize * 2;
1243         draw_beginBoldFont();
1244         drawstring(pos, _("Scoreboard"), sb_heading_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1245         draw_endBoldFont();
1246
1247         pos_y += sb_heading_fontsize_y + hud_fontsize_y * 0.25;
1248
1249         // Draw the scoreboard
1250         vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * autocvar_scoreboard_bg_scale;
1251
1252         if(teamplay)
1253         {
1254                 vector team_score_baseoffset;
1255                 team_score_baseoffset = eY * (2 * autocvar_scoreboard_border_thickness + hud_fontsize_y) - eX * (autocvar_scoreboard_border_thickness + hud_fontsize_x * 0.25);
1256                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1257                 {
1258                         if(tm.team == NUM_SPECTATOR)
1259                                 continue;
1260
1261                         draw_beginBoldFont();
1262                         rgb = Team_ColorRGB(tm.team);
1263                         str = ftos(tm.(teamscores[ts_primary]));
1264                         drawstring(pos + team_score_baseoffset - eX * stringwidth(str, FALSE, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1265
1266                         if(ts_primary != ts_secondary)
1267                         {
1268                                 str = ftos(tm.(teamscores[ts_secondary]));
1269                                 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);
1270                         }
1271                         draw_endBoldFont();
1272
1273                         pos = HUD_Scoreboard_MakeTable(pos, tm, rgb, bg_size);
1274                 }
1275
1276 #ifdef GMQCC
1277                 rgb = '0 0 0';
1278 #endif
1279                 rgb_x = autocvar_scoreboard_color_bg_r;
1280                 rgb_y = autocvar_scoreboard_color_bg_g;
1281                 rgb_z = autocvar_scoreboard_color_bg_b;
1282         }
1283         else
1284         {
1285 #ifdef GMQCC
1286                 rgb = '0 0 0';
1287 #endif
1288                 rgb_x = autocvar_scoreboard_color_bg_r;
1289                 rgb_y = autocvar_scoreboard_color_bg_g;
1290                 rgb_z = autocvar_scoreboard_color_bg_b;
1291
1292                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1293                 {
1294                         if(tm.team == NUM_SPECTATOR)
1295                                 continue;
1296
1297                         pos = HUD_Scoreboard_MakeTable(pos, tm, rgb, bg_size);
1298                 }
1299         }
1300
1301         if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1302                 if(race_speedaward) {
1303                         drawcolorcodedstring(pos, sprintf(_("Speed award: %d ^7(%s^7)"), race_speedaward, race_speedaward_holder), hud_fontsize, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1304                         pos_y += 1.25 * hud_fontsize_y;
1305                 }
1306                 if(race_speedaward_alltimebest) {
1307                         drawcolorcodedstring(pos, sprintf(_("All-time fastest: %d ^7(%s^7)"), race_speedaward_alltimebest, race_speedaward_alltimebest_holder), hud_fontsize, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1308                         pos_y += 1.25 * hud_fontsize_y;
1309                 }
1310                 pos = HUD_DrawScoreboardRankings(pos, playerslots[player_localnum], rgb, bg_size);
1311         }
1312         else if(autocvar_scoreboard_accuracy && spectatee_status == 0 && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL) {
1313                 if(teamplay)
1314                         pos = HUD_DrawScoreboardAccuracyStats(pos, Team_ColorRGB(myteam), bg_size);
1315                 else
1316                         pos = HUD_DrawScoreboardAccuracyStats(pos, rgb, bg_size);
1317         }
1318
1319
1320         if(teamplay)
1321                 pos = HUD_DrawMapStats(pos, Team_ColorRGB(myteam), bg_size);
1322         else
1323                 pos = HUD_DrawMapStats(pos, rgb, bg_size);
1324
1325         // List spectators
1326         float specs;
1327         specs = 0;
1328         tmp = pos;
1329         for(pl = players.sort_next; pl; pl = pl.sort_next)
1330         {
1331                 if(pl.team != NUM_SPECTATOR)
1332                         continue;
1333                 pos_y += 1.25 * hud_fontsize_y;
1334                 HUD_PrintScoreboardItem(pos, pl, (pl.sv_entnum == player_localnum), specs);
1335                 ++specs;
1336         }
1337
1338         if(specs)
1339         {
1340                 draw_beginBoldFont();
1341                 drawstring(tmp, _("Spectators"), hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1342                 draw_endBoldFont();
1343                 pos_y += 1.25 * hud_fontsize_y;
1344         }
1345
1346         // Print info string
1347         float tl, fl, ll;
1348         str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1349         tl = getstatf(STAT_TIMELIMIT);
1350         fl = getstatf(STAT_FRAGLIMIT);
1351         ll = getstatf(STAT_LEADLIMIT);
1352         if(gametype == MAPINFO_TYPE_LMS)
1353         {
1354                 if(tl > 0)
1355                         str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1356         }
1357         else
1358         {
1359                 if(tl > 0)
1360                         str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1361                 if(fl > 0)
1362                 {
1363                         if(tl > 0)
1364                                 str = strcat(str, _(" or"));
1365                         if(teamplay)
1366                         {
1367                                 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags[ts_primary], fl),
1368                                         (teamscores_label[ts_primary] == "score")   ? CTX(_("SCO^points")) :
1369                                         (teamscores_label[ts_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1370                                         TranslateScoresLabel(teamscores_label[ts_primary])));
1371                         }
1372                         else
1373                         {
1374                                 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags[ps_primary], fl),
1375                                         (scores_label[ps_primary] == "score")   ? CTX(_("SCO^points")) :
1376                                         (scores_label[ps_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1377                                         TranslateScoresLabel(scores_label[ps_primary])));
1378                         }
1379                 }
1380                 if(ll > 0)
1381                 {
1382                         if(tl > 0 || fl > 0)
1383                                 str = strcat(str, _(" or"));
1384                         if(teamplay)
1385                         {
1386                                 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags[ts_primary], ll),
1387                                         (teamscores_label[ts_primary] == "score")   ? CTX(_("SCO^points")) :
1388                                         (teamscores_label[ts_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1389                                         TranslateScoresLabel(teamscores_label[ts_primary])));
1390                         }
1391                         else
1392                         {
1393                                 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags[ps_primary], ll),
1394                                         (scores_label[ps_primary] == "score")   ? CTX(_("SCO^points")) :
1395                                         (scores_label[ps_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1396                                         TranslateScoresLabel(scores_label[ps_primary])));
1397                         }
1398                 }
1399         }
1400
1401         pos_y += 1.2 * hud_fontsize_y;
1402         drawcolorcodedstring(pos + '0.5 0 0' * (sbwidth - stringwidth(str, TRUE, hud_fontsize)), str, hud_fontsize, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1403
1404         // print information about respawn status
1405         float respawn_time = getstatf(STAT_RESPAWN_TIME);
1406         if(!intermission)
1407         if(respawn_time)
1408         {
1409                 if(respawn_time < 0)
1410                 {
1411                         // a negative number means we are awaiting respawn, time value is still the same
1412                         respawn_time *= -1; // remove mark now that we checked it
1413                         respawn_time = max(time, respawn_time); // don't show a negative value while the server is respawning the player (lag)
1414
1415                         str = sprintf(_("^1Respawning in ^3%s^1..."),
1416                                 (autocvar_scoreboard_respawntime_decimals ?
1417                                         count_seconds_decs(respawn_time - time, autocvar_scoreboard_respawntime_decimals)
1418                                         :
1419                                         count_seconds(respawn_time - time)
1420                                 )
1421                         );
1422                 }
1423                 else if(time < respawn_time)
1424                 {
1425                         str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1426                                 (autocvar_scoreboard_respawntime_decimals ?
1427                                         count_seconds_decs(respawn_time - time, autocvar_scoreboard_respawntime_decimals)
1428                                         :
1429                                         count_seconds(respawn_time - time)
1430                                 )
1431                         );
1432                 }
1433                 else if(time >= respawn_time)
1434                         str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1435
1436                 pos_y += 1.2 * hud_fontsize_y;
1437                 drawcolorcodedstring(pos + '0.5 0 0' * (sbwidth - stringwidth(str, TRUE, hud_fontsize)), str, hud_fontsize, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1438         }
1439
1440         scoreboard_bottom = pos_y + 2 * hud_fontsize_y;
1441 }