]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/hud/panel/scoreboard.qc
Add a missing check
[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         if(sbt_highlight)
1024         {
1025                 // column highlighting
1026                 for (int i = 0; i < columnns; ++i)
1027                         if ((i % 2) == 0)
1028                                 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1029
1030                 // row highlighting
1031                 for (int i = 0; i < rows; ++i)
1032                         drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * fontsize, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1033         }
1034
1035         average_accuracy = 0;
1036         int weapons_with_stats = 0;
1037         if (rows == 2)
1038                 pos.x += weapon_width / 2;
1039
1040         if (autocvar_scoreboard_accuracy_nocolors)
1041                 rgb = '1 1 1';
1042         else
1043                 Accuracy_LoadColors();
1044
1045         float oldposx = pos.x;
1046         vector tmpos = pos;
1047
1048         int column = 0;
1049         FOREACH(Weapons, it != WEP_Null, {
1050                 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1051
1052                 WepSet set = it.m_wepset;
1053                 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1054                         continue;
1055
1056                 float weapon_alpha;
1057                 if (weapon_stats >= 0)
1058                         weapon_alpha = sbt_fg_alpha;
1059                 else
1060                         weapon_alpha = 0.2 * sbt_fg_alpha;
1061
1062                 // weapon icon
1063                 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1064                 // the accuracy
1065                 if (weapon_stats >= 0) {
1066                         weapons_with_stats += 1;
1067                         average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1068
1069                         string s;
1070                         s = sprintf("%d%%", weapon_stats * 100);
1071
1072                         float padding;
1073                         padding = (weapon_width - stringwidth(s, false, eX * fontsize)) / 2; // center the accuracy value
1074
1075                         if(!autocvar_scoreboard_accuracy_nocolors)
1076                                 rgb = Accuracy_GetColor(weapon_stats);
1077
1078                         drawstring(tmpos + eX * padding + eY * weapon_height, s, '1 1 0' * fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1079                 }
1080                 tmpos.x += weapon_width * rows;
1081                 pos.x += weapon_width * rows;
1082                 if (rows == 2 && column == columnns - 1) {
1083                         tmpos.x = oldposx;
1084                         tmpos.y += height;
1085                         pos.y += height;
1086                 }
1087                 ++column;
1088         });
1089
1090         if (weapons_with_stats)
1091                 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1092
1093         panel_size.x += panel_bg_padding * 2; // restore initial width
1094         return end_pos;
1095 }
1096
1097 vector HUD_DrawKeyValue(vector pos, string key, string value) {
1098         float px = pos.x;
1099         pos.x += hud_fontsize.x * 0.25;
1100         drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1101         pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1102         drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1103         pos.x = px;
1104         pos.y += hud_fontsize.y;
1105
1106         return pos;
1107 }
1108
1109 vector HUD_DrawMapStats(vector pos, vector rgb, vector bg_size) {
1110         float stat_secrets_found, stat_secrets_total;
1111         float stat_monsters_killed, stat_monsters_total;
1112         float rows = 0;
1113         string val;
1114
1115         // get monster stats
1116         stat_monsters_killed = STAT(MONSTERS_KILLED);
1117         stat_monsters_total = STAT(MONSTERS_TOTAL);
1118
1119         // get secrets stats
1120         stat_secrets_found = STAT(SECRETS_FOUND);
1121         stat_secrets_total = STAT(SECRETS_TOTAL);
1122
1123         // get number of rows
1124         if(stat_secrets_total)
1125                 rows += 1;
1126         if(stat_monsters_total)
1127                 rows += 1;
1128
1129         // if no rows, return
1130         if (!rows)
1131                 return pos;
1132
1133         //  draw table header
1134         drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1135         pos.y += 1.25 * hud_fontsize.y;
1136         pos.y += panel_bg_border;
1137
1138         panel_pos = pos;
1139         panel_size.y = hud_fontsize.y * rows;
1140         panel_size.y += panel_bg_padding * 2;
1141         HUD_Panel_DrawBg(scoreboard_fade_alpha);
1142
1143         vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
1144
1145         if(panel_bg_padding)
1146         {
1147                 panel_pos += '1 1 0' * panel_bg_padding;
1148                 panel_size -= '2 2 0' * panel_bg_padding;
1149         }
1150
1151         pos = panel_pos;
1152         vector tmp = panel_size;
1153
1154         if (sbt_bg_alpha)
1155                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1156
1157         // draw monsters
1158         if(stat_monsters_total)
1159         {
1160                 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1161                 pos = HUD_DrawKeyValue(pos, _("Monsters killed:"), val);
1162         }
1163
1164         // draw secrets
1165         if(stat_secrets_total)
1166         {
1167                 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1168                 pos = HUD_DrawKeyValue(pos, _("Secrets found:"), val);
1169         }
1170
1171         panel_size.x += panel_bg_padding * 2; // restore initial width
1172         return end_pos;
1173 }
1174
1175
1176 vector HUD_DrawScoreboardRankings(vector pos, entity pl, vector rgb, vector bg_size)
1177 {
1178         int i;
1179         RANKINGS_RECEIVED_CNT = 0;
1180         for (i=RANKINGS_CNT-1; i>=0; --i)
1181                 if (grecordtime[i])
1182                         ++RANKINGS_RECEIVED_CNT;
1183
1184         if (RANKINGS_RECEIVED_CNT == 0)
1185                 return pos;
1186
1187         vector hl_rgb = rgb + '0.5 0.5 0.5';
1188
1189         pos.y += hud_fontsize.y;
1190         drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1191         pos.y += 1.25 * hud_fontsize.y;
1192         pos.y += panel_bg_border;
1193
1194         panel_pos = pos;
1195         panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1196         panel_size.y += panel_bg_padding * 2;
1197         HUD_Panel_DrawBg(scoreboard_fade_alpha);
1198
1199         vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
1200
1201         if(panel_bg_padding)
1202         {
1203                 panel_pos += '1 1 0' * panel_bg_padding;
1204                 panel_size -= '2 2 0' * panel_bg_padding;
1205         }
1206
1207         pos = panel_pos;
1208         vector tmp = panel_size;
1209
1210         if (sbt_bg_alpha)
1211                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1212
1213         // row highlighting
1214         for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1215         {
1216                 string n, p;
1217                 float t;
1218                 t = grecordtime[i];
1219                 if (t == 0)
1220                         continue;
1221                 n = grecordholder[i];
1222                 p = count_ordinal(i+1);
1223                 if(grecordholder[i] == entcs_GetName(player_localnum))
1224                         drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1225                 else if(!(i % 2) && sbt_highlight)
1226                         drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1227                 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1228                 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);
1229                 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, sbt_fg_alpha, DRAWFLAG_NORMAL);
1230                 pos.y += 1.25 * hud_fontsize.y;
1231         }
1232
1233         panel_size.x += panel_bg_padding * 2; // restore initial width
1234         return end_pos;
1235 }
1236
1237 float hud_woulddrawscoreboard_prev;
1238 float hud_woulddrawscoreboard_change; // "time" at which HUD_WouldDrawScoreboard() changed
1239 void HUD_DrawScoreboard()
1240 {
1241         if(!autocvar__hud_configure)
1242         {
1243                 float hud_woulddrawscoreboard;
1244                 hud_woulddrawscoreboard = scoreboard_active;
1245                 if(hud_woulddrawscoreboard != hud_woulddrawscoreboard_prev) {
1246                         hud_woulddrawscoreboard_change = time;
1247                         hud_woulddrawscoreboard_prev = hud_woulddrawscoreboard;
1248                 }
1249
1250                 if(hud_woulddrawscoreboard) {
1251                         if(menu_enabled == 1)
1252                                 scoreboard_fade_alpha = 1;
1253                         float scoreboard_fadeinspeed = autocvar_scoreboard_fadeinspeed;
1254                         if (scoreboard_fadeinspeed)
1255                                 scoreboard_fade_alpha = bound (0, (time - hud_woulddrawscoreboard_change) * scoreboard_fadeinspeed, 1);
1256                         else
1257                                 scoreboard_fade_alpha = 1;
1258                 }
1259                 else {
1260                         float scoreboard_fadeoutspeed = autocvar_scoreboard_fadeoutspeed;
1261                         if (scoreboard_fadeoutspeed)
1262                                 scoreboard_fade_alpha = bound (0, (1/scoreboard_fadeoutspeed - (time - hud_woulddrawscoreboard_change)) * scoreboard_fadeoutspeed, 1);
1263                         else
1264                                 scoreboard_fade_alpha = 0;
1265                 }
1266
1267                 if (!scoreboard_fade_alpha)
1268                         return;
1269         }
1270         else
1271                 scoreboard_fade_alpha = 0;
1272
1273         if (autocvar_scoreboard_dynamichud)
1274                 HUD_Scale_Enable();
1275         else
1276                 HUD_Scale_Disable();
1277
1278         float hud_fade_alpha_save = hud_fade_alpha;
1279         if(menu_enabled == 1)
1280                 hud_fade_alpha = 1;
1281         else
1282                 hud_fade_alpha = scoreboard_fade_alpha * (1 - autocvar__menu_alpha);
1283         HUD_Panel_UpdateCvars();
1284
1285         sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1286         sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1287         sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1288         sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1289         sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1290         sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1291
1292         hud_fade_alpha = hud_fade_alpha_save;
1293
1294         // don't overlap with con_notify
1295         if(!autocvar__hud_configure)
1296                 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1297
1298         Scoreboard_UpdatePlayerTeams();
1299
1300         vector pos, tmp;
1301         entity pl, tm;
1302         string str;
1303
1304         // Initializes position
1305         pos = panel_pos;
1306
1307         // Heading
1308         vector sb_heading_fontsize;
1309         sb_heading_fontsize = hud_fontsize * 2;
1310         draw_beginBoldFont();
1311         drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1312         draw_endBoldFont();
1313
1314         pos.y += sb_heading_fontsize.y;
1315         pos.y += panel_bg_border;
1316
1317         // Draw the scoreboard
1318         vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * ((autocvar_scoreboard_bg_scale > 0) ? autocvar_scoreboard_bg_scale : 0.25);
1319
1320         if(teamplay)
1321         {
1322                 vector team_score_baseoffset = eY * hud_fontsize.y - eX * (panel_bg_border + hud_fontsize.x * 0.5);
1323                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1324                 {
1325                         if(tm.team == NUM_SPECTATOR)
1326                                 continue;
1327                         if(!tm.team && teamplay)
1328                                 continue;
1329
1330                         draw_beginBoldFont();
1331                         vector rgb = Team_ColorRGB(tm.team);
1332                         str = ftos(tm.(teamscores[ts_primary]));
1333                         drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1334
1335                         if(ts_primary != ts_secondary)
1336                         {
1337                                 str = ftos(tm.(teamscores[ts_secondary]));
1338                                 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);
1339                         }
1340                         draw_endBoldFont();
1341
1342                         panel_bg_color = rgb * panel_bg_color_team;
1343                         pos = HUD_Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1344                 }
1345                 panel_bg_color = Team_ColorRGB(myteam) * panel_bg_color_team;
1346         }
1347         else
1348         {
1349                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1350                 {
1351                         if(tm.team == NUM_SPECTATOR)
1352                                 continue;
1353                         if(!tm.team && teamplay)
1354                                 continue;
1355
1356                         pos = HUD_Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1357                 }
1358         }
1359
1360         if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1361                 if(race_speedaward) {
1362                         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);
1363                         pos.y += 1.25 * hud_fontsize.y;
1364                 }
1365                 if(race_speedaward_alltimebest) {
1366                         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);
1367                         pos.y += 1.25 * hud_fontsize.y;
1368                 }
1369                 pos = HUD_DrawScoreboardRankings(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1370         }
1371         else if (autocvar_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL) {
1372                 pos = HUD_DrawScoreboardAccuracyStats(pos, panel_bg_color, bg_size);
1373         }
1374
1375         pos = HUD_DrawMapStats(pos, panel_bg_color, bg_size);
1376
1377         // List spectators
1378         float specs = 0;
1379         tmp = pos;
1380         for(pl = players.sort_next; pl; pl = pl.sort_next)
1381         {
1382                 if(pl.team != NUM_SPECTATOR)
1383                         continue;
1384                 pos.y += 1.25 * hud_fontsize.y;
1385                 HUD_PrintScoreboardItem(pos, panel_bg_color, pl, (pl.sv_entnum == player_localnum), specs);
1386                 ++specs;
1387         }
1388
1389         if(specs)
1390         {
1391                 draw_beginBoldFont();
1392                 drawstring(tmp, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1393                 draw_endBoldFont();
1394                 pos.y += 1.25 * hud_fontsize.y;
1395         }
1396
1397         // Print info string
1398         float tl, fl, ll;
1399         str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1400         tl = STAT(TIMELIMIT);
1401         fl = STAT(FRAGLIMIT);
1402         ll = STAT(LEADLIMIT);
1403         if(gametype == MAPINFO_TYPE_LMS)
1404         {
1405                 if(tl > 0)
1406                         str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1407         }
1408         else
1409         {
1410                 if(tl > 0)
1411                         str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1412                 if(fl > 0)
1413                 {
1414                         if(tl > 0)
1415                                 str = strcat(str, _(" or"));
1416                         if(teamplay)
1417                         {
1418                                 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags[ts_primary], fl),
1419                                         (teamscores_label[ts_primary] == "score")   ? CTX(_("SCO^points")) :
1420                                         (teamscores_label[ts_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1421                                         TranslateScoresLabel(teamscores_label[ts_primary])));
1422                         }
1423                         else
1424                         {
1425                                 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags[ps_primary], fl),
1426                                         (scores_label[ps_primary] == "score")   ? CTX(_("SCO^points")) :
1427                                         (scores_label[ps_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1428                                         TranslateScoresLabel(scores_label[ps_primary])));
1429                         }
1430                 }
1431                 if(ll > 0)
1432                 {
1433                         if(tl > 0 || fl > 0)
1434                                 str = strcat(str, _(" or"));
1435                         if(teamplay)
1436                         {
1437                                 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags[ts_primary], ll),
1438                                         (teamscores_label[ts_primary] == "score")   ? CTX(_("SCO^points")) :
1439                                         (teamscores_label[ts_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1440                                         TranslateScoresLabel(teamscores_label[ts_primary])));
1441                         }
1442                         else
1443                         {
1444                                 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags[ps_primary], ll),
1445                                         (scores_label[ps_primary] == "score")   ? CTX(_("SCO^points")) :
1446                                         (scores_label[ps_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1447                                         TranslateScoresLabel(scores_label[ps_primary])));
1448                         }
1449                 }
1450         }
1451
1452         pos.y += 1.2 * hud_fontsize.y;
1453         drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1454
1455         // print information about respawn status
1456         float respawn_time = STAT(RESPAWN_TIME);
1457         if(!intermission)
1458         if(respawn_time)
1459         {
1460                 if(respawn_time < 0)
1461                 {
1462                         // a negative number means we are awaiting respawn, time value is still the same
1463                         respawn_time *= -1; // remove mark now that we checked it
1464                         respawn_time = max(time, respawn_time); // don't show a negative value while the server is respawning the player (lag)
1465
1466                         str = sprintf(_("^1Respawning in ^3%s^1..."),
1467                                 (autocvar_scoreboard_respawntime_decimals ?
1468                                         count_seconds_decs(respawn_time - time, autocvar_scoreboard_respawntime_decimals)
1469                                         :
1470                                         count_seconds(respawn_time - time)
1471                                 )
1472                         );
1473                 }
1474                 else if(time < respawn_time)
1475                 {
1476                         str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1477                                 (autocvar_scoreboard_respawntime_decimals ?
1478                                         count_seconds_decs(respawn_time - time, autocvar_scoreboard_respawntime_decimals)
1479                                         :
1480                                         count_seconds(respawn_time - time)
1481                                 )
1482                         );
1483                 }
1484                 else if(time >= respawn_time)
1485                         str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1486
1487                 pos.y += 1.2 * hud_fontsize.y;
1488                 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1489         }
1490
1491         scoreboard_bottom = pos.y + 2 * hud_fontsize.y;
1492 }