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