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