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