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