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