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