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