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