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