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