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