]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/hud/panel/scoreboard.qc
Merge branch 'Lyberta/OKShotgunAmmoUsage' into 'master'
[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_GetName(entity pl)
573 {
574         if(ready_waiting && pl.ready)
575         {
576                 sbt_field_icon0 = "gfx/scoreboard/player_ready";
577         }
578         else if(!teamplay)
579         {
580                 int f = entcs_GetClientColors(pl.sv_entnum);
581                 {
582                         sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
583                         sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
584                         sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
585                         sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
586                         sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
587                 }
588         }
589         return entcs_GetName(pl.sv_entnum);
590 }
591 string Scoreboard_GetField(entity pl, PlayerScoreField field)
592 {
593         float tmp, num, denom;
594         int f;
595         string str;
596         sbt_field_rgb = '1 1 1';
597         sbt_field_icon0 = "";
598         sbt_field_icon1 = "";
599         sbt_field_icon2 = "";
600         sbt_field_icon0_rgb = '1 1 1';
601         sbt_field_icon1_rgb = '1 1 1';
602         sbt_field_icon2_rgb = '1 1 1';
603         switch(field)
604         {
605                 case SP_PING:
606                         if (!pl.gotscores)
607                                 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
608                         //str = getplayerkeyvalue(pl.sv_entnum, "ping");
609                         f = pl.ping;
610                         if(f == 0)
611                                 return _("N/A");
612                         tmp = max(0, min(220, f-80)) / 220;
613                         sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
614                         return ftos(f);
615
616                 case SP_PL:
617                         if (!pl.gotscores)
618                                 return _("N/A");
619                         f = pl.ping_packetloss;
620                         tmp = pl.ping_movementloss;
621                         if(f == 0 && tmp == 0)
622                                 return "";
623                         str = ftos(ceil(f * 100));
624                         if(tmp != 0)
625                                 str = strcat(str, "~", ftos(ceil(tmp * 100)));
626                         tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
627                         sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
628                         return str;
629
630                 case SP_NAME:
631                         return Scoreboard_GetName(pl);
632
633                 case SP_FRAGS:
634                         f = pl.(scores(SP_KILLS));
635                         f -= pl.(scores(SP_SUICIDES));
636                         return ftos(f);
637
638                 case SP_KDRATIO:
639                         num = pl.(scores(SP_KILLS));
640                         denom = pl.(scores(SP_DEATHS));
641
642                         if(denom == 0) {
643                                 sbt_field_rgb = '0 1 0';
644                                 str = sprintf("%d", num);
645                         } else if(num <= 0) {
646                                 sbt_field_rgb = '1 0 0';
647                                 str = sprintf("%.1f", num/denom);
648                         } else
649                                 str = sprintf("%.1f", num/denom);
650                         return str;
651
652                 case SP_SUM:
653                         f = pl.(scores(SP_KILLS));
654                         f -= pl.(scores(SP_DEATHS));
655
656                         if(f > 0) {
657                                 sbt_field_rgb = '0 1 0';
658                         } else if(f == 0) {
659                                 sbt_field_rgb = '1 1 1';
660                         } else {
661                                 sbt_field_rgb = '1 0 0';
662                         }
663                         return ftos(f);
664
665                 case SP_ELO:
666                 {
667                         float elo = pl.(scores(SP_ELO));
668                         switch (elo) {
669                                 case -1: return "...";
670                                 case -2: return _("N/A");
671                                 default: return ftos(elo);
672                         }
673                 }
674
675                 case SP_DMG: case SP_DMGTAKEN:
676                         return sprintf("%.1f k", pl.(scores(field)) / 1000);
677
678                 default: case SP_SCORE:
679                         tmp = pl.(scores(field));
680                         f = scores_flags(field);
681                         if(field == ps_primary)
682                                 sbt_field_rgb = '1 1 0';
683                         else if(field == ps_secondary)
684                                 sbt_field_rgb = '0 1 1';
685                         else
686                                 sbt_field_rgb = '1 1 1';
687                         return ScoreString(f, tmp);
688         }
689         //return "error";
690 }
691
692 float sbt_fixcolumnwidth_len;
693 float sbt_fixcolumnwidth_iconlen;
694 float sbt_fixcolumnwidth_marginlen;
695
696 string Scoreboard_FixColumnWidth(int i, string str)
697 {
698     TC(int, i);
699         float f;
700         vector sz;
701
702         sbt_fixcolumnwidth_iconlen = 0;
703
704         if(sbt_field_icon0 != "")
705         {
706                 sz = draw_getimagesize(sbt_field_icon0);
707                 f = sz.x / sz.y;
708                 if(sbt_fixcolumnwidth_iconlen < f)
709                         sbt_fixcolumnwidth_iconlen = f;
710         }
711
712         if(sbt_field_icon1 != "")
713         {
714                 sz = draw_getimagesize(sbt_field_icon1);
715                 f = sz.x / sz.y;
716                 if(sbt_fixcolumnwidth_iconlen < f)
717                         sbt_fixcolumnwidth_iconlen = f;
718         }
719
720         if(sbt_field_icon2 != "")
721         {
722                 sz = draw_getimagesize(sbt_field_icon2);
723                 f = sz.x / sz.y;
724                 if(sbt_fixcolumnwidth_iconlen < f)
725                         sbt_fixcolumnwidth_iconlen = f;
726         }
727
728         if(sbt_fixcolumnwidth_iconlen != 0)
729         {
730                 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
731                 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
732         }
733         else
734                 sbt_fixcolumnwidth_marginlen = 0;
735
736         if(sbt_field[i] == SP_NAME) // name gets all remaining space
737         {
738                 int j;
739                 float remaining_space = 0;
740                 for(j = 0; j < sbt_num_fields; ++j)
741                         if(j != i)
742                                 if (sbt_field[i] != SP_SEPARATOR)
743                                         remaining_space += sbt_field_size[j] + hud_fontsize.x;
744                 sbt_field_size[i] = panel_size.x - remaining_space;
745
746                 if (sbt_fixcolumnwidth_iconlen != 0)
747                         remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
748                 float namesize = panel_size.x - remaining_space;
749                 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
750                 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
751
752                 max_namesize = vid_conwidth - remaining_space;
753         }
754         else
755                 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
756
757         f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
758         if(sbt_field_size[i] < f)
759                 sbt_field_size[i] = f;
760
761         return str;
762 }
763
764 void Scoreboard_initFieldSizes()
765 {
766         for(int i = 0; i < sbt_num_fields; ++i)
767         {
768                 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
769                 Scoreboard_FixColumnWidth(i, "");
770         }
771 }
772
773 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
774 {
775         int i;
776         vector column_dim = eY * panel_size.y;
777         if(other_players)
778                 column_dim.y -= 1.25 * hud_fontsize.y;
779         vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
780         pos.x += hud_fontsize.x * 0.5;
781         for(i = 0; i < sbt_num_fields; ++i)
782         {
783                 if(sbt_field[i] == SP_SEPARATOR)
784                         break;
785                 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
786                 if (sbt_highlight)
787                         if (i % 2)
788                                 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
789                 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
790                 pos.x += column_dim.x;
791         }
792         if(sbt_field[i] == SP_SEPARATOR)
793         {
794                 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
795                 for(i = sbt_num_fields - 1; i > 0; --i)
796                 {
797                         if(sbt_field[i] == SP_SEPARATOR)
798                                 break;
799
800                         pos.x -= sbt_field_size[i];
801
802                         if (sbt_highlight)
803                                 if (!(i % 2))
804                                 {
805                                         column_dim.x = sbt_field_size[i] + hud_fontsize.x;
806                                         drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
807                                 }
808
809                         text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
810                         drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
811                         pos.x -= hud_fontsize.x;
812                 }
813         }
814
815         pos.x = panel_pos.x;
816         pos.y += 1.25 * hud_fontsize.y;
817         return pos;
818 }
819
820 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
821 {
822     TC(bool, is_self); TC(int, pl_number);
823         string str;
824         bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
825
826         vector h_pos = item_pos;
827         vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
828         // alternated rows highlighting
829         if(is_self)
830                 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
831         else if((sbt_highlight) && (!(pl_number % 2)))
832                 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
833
834         float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
835
836         vector pos = item_pos;
837         pos.x += hud_fontsize.x * 0.5;
838         pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
839         vector tmp = '0 0 0';
840         int i;
841         PlayerScoreField field;
842         for(i = 0; i < sbt_num_fields; ++i)
843         {
844                 field = sbt_field[i];
845                 if(field == SP_SEPARATOR)
846                         break;
847
848                 if(is_spec && field != SP_NAME && field != SP_PING) {
849                         pos.x += sbt_field_size[i] + hud_fontsize.x;
850                         continue;
851                 }
852                 str = Scoreboard_GetField(pl, field);
853                 str = Scoreboard_FixColumnWidth(i, str);
854
855                 pos.x += sbt_field_size[i] + hud_fontsize.x;
856
857                 if(field == SP_NAME) {
858                         tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
859                         drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
860                 } else {
861                         tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
862                         drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
863                 }
864
865                 tmp.x = sbt_field_size[i] + hud_fontsize.x;
866                 if(sbt_field_icon0 != "")
867                         drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
868                 if(sbt_field_icon1 != "")
869                         drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
870                 if(sbt_field_icon2 != "")
871                         drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
872         }
873
874         if(sbt_field[i] == SP_SEPARATOR)
875         {
876                 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
877                 for(i = sbt_num_fields-1; i > 0; --i)
878                 {
879                         field = sbt_field[i];
880                         if(field == SP_SEPARATOR)
881                                 break;
882
883                         if(is_spec && field != SP_NAME && field != SP_PING) {
884                                 pos.x -= sbt_field_size[i] + hud_fontsize.x;
885                                 continue;
886                         }
887
888                         str = Scoreboard_GetField(pl, field);
889                         str = Scoreboard_FixColumnWidth(i, str);
890
891                         if(field == SP_NAME) {
892                                 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
893                                 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
894                         } else {
895                                 tmp.x = sbt_fixcolumnwidth_len;
896                                 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
897                         }
898
899                         tmp.x = sbt_field_size[i];
900                         if(sbt_field_icon0 != "")
901                                 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
902                         if(sbt_field_icon1 != "")
903                                 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
904                         if(sbt_field_icon2 != "")
905                                 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
906                         pos.x -= sbt_field_size[i] + hud_fontsize.x;
907                 }
908         }
909
910         if(pl.eliminated)
911                 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
912 }
913
914 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
915 {
916         int i = 0;
917         vector h_pos = item_pos;
918         vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
919
920         bool complete = (this_team == NUM_SPECTATOR);
921
922         if(!complete)
923         if((sbt_highlight) && (!(pl_number % 2)))
924                 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
925
926         vector pos = item_pos;
927         pos.x += hud_fontsize.x * 0.5;
928         pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
929
930         float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
931         if(!complete)
932                 width_limit -= stringwidth("...", false, hud_fontsize);
933         float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
934         static float max_name_width = 0;
935         string field = "";
936         float fieldsize = 0;
937         float min_fieldsize = 0;
938         float fieldpadding = hud_fontsize.x * 0.25;
939         if(this_team == NUM_SPECTATOR)
940         {
941                 if(autocvar_hud_panel_scoreboard_spectators_showping)
942                         min_fieldsize = stringwidth("999", false, hud_fontsize);
943         }
944         else if(autocvar_hud_panel_scoreboard_others_showscore)
945                 min_fieldsize = stringwidth("99", false, hud_fontsize);
946         for(i = 0; pl; pl = pl.sort_next)
947         {
948                 if(pl.team != this_team)
949                         continue;
950                 if(pl == ignored_pl)
951                         continue;
952
953                 field = "";
954                 if(this_team == NUM_SPECTATOR)
955                 {
956                         if(autocvar_hud_panel_scoreboard_spectators_showping)
957                                 field = Scoreboard_GetField(pl, SP_PING);
958                 }
959                 else if(autocvar_hud_panel_scoreboard_others_showscore)
960                         field = Scoreboard_GetField(pl, SP_SCORE);
961
962                 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
963                 float column_width = stringwidth(str, true, hud_fontsize);
964                 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
965                 {
966                         if(column_width > max_name_width)
967                                 max_name_width = column_width;
968                         column_width = max_name_width;
969                 }
970                 if(field != "")
971                 {
972                         fieldsize = stringwidth(field, false, hud_fontsize);
973                         column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
974                 }
975
976                 if(pos.x + column_width > width_limit)
977                 {
978                         ++i;
979                         if(!complete)
980                         {
981                                 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
982                                 break;
983                         }
984                         else
985                         {
986                                 pos.x = item_pos.x + hud_fontsize.x * 0.5;
987                                 pos.y += hud_fontsize.y * 1.25;
988                         }
989                 }
990
991                 vector name_pos = pos;
992                 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
993                         name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
994                 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
995                 if(field != "")
996                 {
997                         h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
998                         h_size.y = hud_fontsize.y;
999                         vector field_pos = pos;
1000                         if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1001                                 field_pos.x += column_width - h_size.x;
1002                         if(sbt_highlight)
1003                                 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1004                         field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1005                         drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1006                 }
1007                 pos.x += column_width;
1008                 pos.x += hud_fontsize.x;
1009         }
1010         return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1011 }
1012
1013 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1014 {
1015         int max_players = 999;
1016         if(autocvar_hud_panel_scoreboard_maxheight > 0)
1017         {
1018                 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1019                 if(teamplay)
1020                 {
1021                         height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1022                         height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1023                         height /= team_count;
1024                 }
1025                 else
1026                         height -= panel_bg_padding * 2; // - padding
1027                 max_players = floor(height / (hud_fontsize.y * 1.25));
1028                 if(max_players <= 1)
1029                         max_players = 1;
1030                 if(max_players == tm.team_size)
1031                         max_players = 999;
1032         }
1033
1034         entity pl;
1035         entity me = playerslots[current_player];
1036         panel_pos = pos;
1037         panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1038         panel_size.y += panel_bg_padding * 2;
1039         HUD_Panel_DrawBg();
1040
1041         vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1042         if(panel.current_panel_bg != "0")
1043                 end_pos.y += panel_bg_border * 2;
1044
1045         if(panel_bg_padding)
1046         {
1047                 panel_pos += '1 1 0' * panel_bg_padding;
1048                 panel_size -= '2 2 0' * panel_bg_padding;
1049         }
1050
1051         pos = panel_pos;
1052         vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1053
1054         // rounded header
1055         if (sbt_bg_alpha)
1056                 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1057
1058         pos.y += 1.25 * hud_fontsize.y;
1059
1060         // table background
1061         tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1062         if (sbt_bg_alpha)
1063                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1064
1065
1066         // print header row and highlight columns
1067         pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1068
1069         // fill the table and draw the rows
1070         bool is_self = false;
1071         bool self_shown = false;
1072         int i = 0;
1073         for(pl = players.sort_next; pl; pl = pl.sort_next)
1074         {
1075                 if(pl.team != tm.team)
1076                         continue;
1077                 if(i == max_players - 2 && pl != me)
1078                 {
1079                         if(!self_shown && me.team == tm.team)
1080                         {
1081                                 Scoreboard_DrawItem(pos, rgb, me, true, i);
1082                                 self_shown = true;
1083                                 pos.y += 1.25 * hud_fontsize.y;
1084                                 ++i;
1085                         }
1086                 }
1087                 if(i >= max_players - 1)
1088                 {
1089                         pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1090                         break;
1091                 }
1092                 is_self = (pl.sv_entnum == current_player);
1093                 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1094                 if(is_self)
1095                         self_shown = true;
1096                 pos.y += 1.25 * hud_fontsize.y;
1097                 ++i;
1098         }
1099
1100         panel_size.x += panel_bg_padding * 2; // restore initial width
1101         return end_pos;
1102 }
1103
1104 bool Scoreboard_WouldDraw()
1105 {
1106         if (MUTATOR_CALLHOOK(DrawScoreboard))
1107                 return false;
1108         else if (QuickMenu_IsOpened())
1109                 return false;
1110         else if (HUD_Radar_Clickable())
1111                 return false;
1112         else if (scoreboard_showscores)
1113                 return true;
1114         else if (intermission == 1)
1115                 return true;
1116         else if (intermission == 2)
1117                 return false;
1118         else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1119                 return true;
1120         else if (scoreboard_showscores_force)
1121                 return true;
1122         return false;
1123 }
1124
1125 float average_accuracy;
1126 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1127 {
1128         WepSet weapons_stat = WepSet_GetFromStat();
1129         WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1130         int disownedcnt = 0;
1131         int nHidden = 0;
1132         FOREACH(Weapons, it != WEP_Null, {
1133                 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1134
1135                 WepSet set = it.m_wepset;
1136                 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1137                 {
1138                         if (((it.spawnflags & WEP_FLAG_HIDDEN) || (it.spawnflags & WEP_FLAG_MUTATORBLOCKED)))
1139                                 ++nHidden;
1140                         else
1141                                 ++disownedcnt;
1142                 }
1143         });
1144
1145         int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1146         if (weapon_cnt <= 0) return pos;
1147
1148         int rows = 1;
1149         if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1150                 rows = 2;
1151         int columnns = ceil(weapon_cnt / rows);
1152
1153         float weapon_height = 29;
1154         float height = hud_fontsize.y + weapon_height;
1155
1156         drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1157         pos.y += 1.25 * hud_fontsize.y;
1158         if(panel.current_panel_bg != "0")
1159                 pos.y += panel_bg_border;
1160
1161         panel_pos = pos;
1162         panel_size.y = height * rows;
1163         panel_size.y += panel_bg_padding * 2;
1164         HUD_Panel_DrawBg();
1165
1166         vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1167         if(panel.current_panel_bg != "0")
1168                 end_pos.y += panel_bg_border * 2;
1169
1170         if(panel_bg_padding)
1171         {
1172                 panel_pos += '1 1 0' * panel_bg_padding;
1173                 panel_size -= '2 2 0' * panel_bg_padding;
1174         }
1175
1176         pos = panel_pos;
1177         vector tmp = panel_size;
1178
1179         float weapon_width = tmp.x / columnns / rows;
1180
1181         if (sbt_bg_alpha)
1182                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1183
1184         if(sbt_highlight)
1185         {
1186                 // column highlighting
1187                 for (int i = 0; i < columnns; ++i)
1188                         if ((i % 2) == 0)
1189                                 drawfill(pos + eX * weapon_width * rows * i, vec2(weapon_width * rows, height * rows), '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1190
1191                 // row highlighting
1192                 for (int i = 0; i < rows; ++i)
1193                         drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1194         }
1195
1196         average_accuracy = 0;
1197         int weapons_with_stats = 0;
1198         if (rows == 2)
1199                 pos.x += weapon_width / 2;
1200
1201         if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1202                 rgb = '1 1 1';
1203         else
1204                 Accuracy_LoadColors();
1205
1206         float oldposx = pos.x;
1207         vector tmpos = pos;
1208
1209         int column = 0;
1210         FOREACH(Weapons, it != WEP_Null, {
1211                 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1212
1213                 WepSet set = it.m_wepset;
1214                 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1215                         continue;
1216
1217                 float weapon_alpha;
1218                 if (weapon_stats >= 0)
1219                         weapon_alpha = sbt_fg_alpha;
1220                 else
1221                         weapon_alpha = 0.2 * sbt_fg_alpha;
1222
1223                 // weapon icon
1224                 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1225                 // the accuracy
1226                 if (weapon_stats >= 0) {
1227                         weapons_with_stats += 1;
1228                         average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1229
1230                         string s;
1231                         s = sprintf("%d%%", weapon_stats * 100);
1232
1233                         float padding;
1234                         padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1235
1236                         if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1237                                 rgb = Accuracy_GetColor(weapon_stats);
1238
1239                         drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1240                 }
1241                 tmpos.x += weapon_width * rows;
1242                 pos.x += weapon_width * rows;
1243                 if (rows == 2 && column == columnns - 1) {
1244                         tmpos.x = oldposx;
1245                         tmpos.y += height;
1246                         pos.y += height;
1247                 }
1248                 ++column;
1249         });
1250
1251         if (weapons_with_stats)
1252                 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1253
1254         panel_size.x += panel_bg_padding * 2; // restore initial width
1255         return end_pos;
1256 }
1257
1258 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1259         float px = pos.x;
1260         pos.x += hud_fontsize.x * 0.25;
1261         drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1262         pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1263         drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1264         pos.x = px;
1265         pos.y += hud_fontsize.y;
1266
1267         return pos;
1268 }
1269
1270 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1271         float stat_secrets_found, stat_secrets_total;
1272         float stat_monsters_killed, stat_monsters_total;
1273         float rows = 0;
1274         string val;
1275
1276         // get monster stats
1277         stat_monsters_killed = STAT(MONSTERS_KILLED);
1278         stat_monsters_total = STAT(MONSTERS_TOTAL);
1279
1280         // get secrets stats
1281         stat_secrets_found = STAT(SECRETS_FOUND);
1282         stat_secrets_total = STAT(SECRETS_TOTAL);
1283
1284         // get number of rows
1285         if(stat_secrets_total)
1286                 rows += 1;
1287         if(stat_monsters_total)
1288                 rows += 1;
1289
1290         // if no rows, return
1291         if (!rows)
1292                 return pos;
1293
1294         //  draw table header
1295         drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1296         pos.y += 1.25 * hud_fontsize.y;
1297         if(panel.current_panel_bg != "0")
1298                 pos.y += panel_bg_border;
1299
1300         panel_pos = pos;
1301         panel_size.y = hud_fontsize.y * rows;
1302         panel_size.y += panel_bg_padding * 2;
1303         HUD_Panel_DrawBg();
1304
1305         vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1306         if(panel.current_panel_bg != "0")
1307                 end_pos.y += panel_bg_border * 2;
1308
1309         if(panel_bg_padding)
1310         {
1311                 panel_pos += '1 1 0' * panel_bg_padding;
1312                 panel_size -= '2 2 0' * panel_bg_padding;
1313         }
1314
1315         pos = panel_pos;
1316         vector tmp = panel_size;
1317
1318         if (sbt_bg_alpha)
1319                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1320
1321         // draw monsters
1322         if(stat_monsters_total)
1323         {
1324                 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1325                 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1326         }
1327
1328         // draw secrets
1329         if(stat_secrets_total)
1330         {
1331                 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1332                 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1333         }
1334
1335         panel_size.x += panel_bg_padding * 2; // restore initial width
1336         return end_pos;
1337 }
1338
1339
1340 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1341 {
1342         int i;
1343         RANKINGS_RECEIVED_CNT = 0;
1344         for (i=RANKINGS_CNT-1; i>=0; --i)
1345                 if (grecordtime[i])
1346                         ++RANKINGS_RECEIVED_CNT;
1347
1348         if (RANKINGS_RECEIVED_CNT == 0)
1349                 return pos;
1350
1351         vector hl_rgb = rgb + '0.5 0.5 0.5';
1352
1353         pos.y += hud_fontsize.y;
1354         drawstring(pos + eX * panel_bg_padding, ((gametype == MAPINFO_TYPE_CTF) ? _("Capture time rankings") : _("Rankings")), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1355         pos.y += 1.25 * hud_fontsize.y;
1356         if(panel.current_panel_bg != "0")
1357                 pos.y += panel_bg_border;
1358
1359         panel_pos = pos;
1360
1361         float namesize = 0;
1362         for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1363         {
1364                 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1365                 if(f > namesize)
1366                         namesize = f;
1367         }
1368         bool cut = false;
1369         if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1370         {
1371                 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1372                 cut = true;
1373         }
1374
1375         float ranksize = 3 * hud_fontsize.x;
1376         float timesize = 5 * hud_fontsize.x;
1377         vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1378         int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1379         columns = min(columns, RANKINGS_RECEIVED_CNT);
1380
1381         // expand name column to fill the entire row
1382         float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1383         namesize += available_space;
1384         columnsize.x += available_space;
1385
1386         panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1387         panel_size.y += panel_bg_padding * 2;
1388
1389         HUD_Panel_DrawBg();
1390
1391         vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1392         if(panel.current_panel_bg != "0")
1393                 end_pos.y += panel_bg_border * 2;
1394
1395         if(panel_bg_padding)
1396         {
1397                 panel_pos += '1 1 0' * panel_bg_padding;
1398                 panel_size -= '2 2 0' * panel_bg_padding;
1399         }
1400
1401         pos = panel_pos;
1402
1403         if (sbt_bg_alpha)
1404                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1405
1406         vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1407         string str = "";
1408         int column = 0, j = 0;
1409         for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1410         {
1411                 float t;
1412                 t = grecordtime[i];
1413                 if (t == 0)
1414                         continue;
1415
1416                 if(strdecolorize(grecordholder[i]) == strdecolorize(entcs_GetName(player_localnum)))
1417                         drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1418                 else if(!((j + column) & 1) && sbt_highlight)
1419                         drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1420
1421                 str = count_ordinal(i+1);
1422                 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1423                 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1424                 str = ColorTranslateRGB(grecordholder[i]);
1425                 if(cut)
1426                         str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1427                 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1428
1429                 pos.y += 1.25 * hud_fontsize.y;
1430                 j++;
1431                 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1432                 {
1433                         column++;
1434                         j = 0;
1435                         pos.x += panel_size.x / columns;
1436                         pos.y = panel_pos.y;
1437                 }
1438         }
1439
1440         panel_size.x += panel_bg_padding * 2; // restore initial width
1441         return end_pos;
1442 }
1443
1444 void Scoreboard_Draw()
1445 {
1446         if(!autocvar__hud_configure)
1447         {
1448                 if(!hud_draw_maximized) return;
1449
1450                 // frametime checks allow to toggle the scoreboard even when the game is paused
1451                 if(scoreboard_active) {
1452                         if(hud_configure_menu_open == 1)
1453                                 scoreboard_fade_alpha = 1;
1454                         float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1455                         if (scoreboard_fadeinspeed && frametime)
1456                                 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1457                         else
1458                                 scoreboard_fade_alpha = 1;
1459                         if(hud_fontsize_str != autocvar_hud_fontsize)
1460                         {
1461                                 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1462                                 Scoreboard_initFieldSizes();
1463                                 if(hud_fontsize_str)
1464                                         strunzone(hud_fontsize_str);
1465                                 hud_fontsize_str = strzone(autocvar_hud_fontsize);
1466                         }
1467                 }
1468                 else {
1469                         float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1470                         if (scoreboard_fadeoutspeed && frametime)
1471                                 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1472                         else
1473                                 scoreboard_fade_alpha = 0;
1474                 }
1475
1476                 if (!scoreboard_fade_alpha)
1477                         return;
1478         }
1479         else
1480                 scoreboard_fade_alpha = 0;
1481
1482         if (autocvar_hud_panel_scoreboard_dynamichud)
1483                 HUD_Scale_Enable();
1484         else
1485                 HUD_Scale_Disable();
1486
1487         if(scoreboard_fade_alpha <= 0)
1488                 return;
1489         panel_fade_alpha *= scoreboard_fade_alpha;
1490         HUD_Panel_LoadCvars();
1491
1492         sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1493         sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1494         sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1495         sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1496         sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1497         sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1498
1499         // don't overlap with con_notify
1500         if(!autocvar__hud_configure)
1501                 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1502
1503         float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1504         float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1505         panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1506         panel_size.x = fixed_scoreboard_width;
1507
1508         Scoreboard_UpdatePlayerTeams();
1509
1510         vector pos = panel_pos;
1511         entity pl, tm;
1512         string str;
1513         vector str_pos;
1514
1515         // Heading
1516         vector sb_heading_fontsize;
1517         sb_heading_fontsize = hud_fontsize * 2;
1518         draw_beginBoldFont();
1519         drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1520         draw_endBoldFont();
1521
1522         pos.y += sb_heading_fontsize.y;
1523         if(panel.current_panel_bg != "0")
1524                 pos.y += panel_bg_border;
1525
1526         // Draw the scoreboard
1527         float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1528         if(scale <= 0)
1529                 scale = 0.25;
1530         vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1531
1532         if(teamplay)
1533         {
1534                 vector panel_bg_color_save = panel_bg_color;
1535                 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1536                 if(panel.current_panel_bg != "0")
1537                         team_score_baseoffset.x -= panel_bg_border;
1538                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1539                 {
1540                         if(tm.team == NUM_SPECTATOR)
1541                                 continue;
1542                         if(!tm.team)
1543                                 continue;
1544
1545                         draw_beginBoldFont();
1546                         vector rgb = Team_ColorRGB(tm.team);
1547                         str = ftos(tm.(teamscores(ts_primary)));
1548                         str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1549                         drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1550
1551                         if(ts_primary != ts_secondary)
1552                         {
1553                                 str = ftos(tm.(teamscores(ts_secondary)));
1554                                 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * 1.5);
1555                                 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1556                         }
1557                         draw_endBoldFont();
1558                         if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1559                                 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1560                         else if(panel_bg_color_team > 0)
1561                                 panel_bg_color = rgb * panel_bg_color_team;
1562                         else
1563                                 panel_bg_color = rgb;
1564                         pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1565                 }
1566                 panel_bg_color = panel_bg_color_save;
1567         }
1568         else
1569         {
1570                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1571                         if(tm.team != NUM_SPECTATOR)
1572                                 break;
1573                 // display it anyway
1574                 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1575         }
1576
1577         bool show_accuracy = (gametype != MAPINFO_TYPE_CTS && gametype != MAPINFO_TYPE_RACE && gametype != MAPINFO_TYPE_NEXBALL);
1578
1579         if (show_accuracy && autocvar_hud_panel_scoreboard_accuracy && !warmup_stage)
1580                 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1581
1582         if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE || (autocvar_hud_panel_scoreboard_ctf_leaderboard && gametype == MAPINFO_TYPE_CTF && STAT(CTF_SHOWLEADERBOARD))) {
1583                 if(race_speedaward) {
1584                         drawcolorcodedstring(pos, sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, race_speedaward_unit, ColorTranslateRGB(race_speedaward_holder)), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1585                         pos.y += 1.25 * hud_fontsize.y;
1586                 }
1587                 if(race_speedaward_alltimebest) {
1588                         drawcolorcodedstring(pos, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, race_speedaward_alltimebest_unit, ColorTranslateRGB(race_speedaward_alltimebest_holder)), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1589                         pos.y += 1.25 * hud_fontsize.y;
1590                 }
1591                 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1592         }
1593
1594         pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1595
1596         // List spectators
1597         for(pl = players.sort_next; pl; pl = pl.sort_next)
1598         {
1599                 if(pl.team == NUM_SPECTATOR)
1600                 {
1601                         for(tm = teams.sort_next; tm; tm = tm.sort_next)
1602                                 if(tm.team == NUM_SPECTATOR)
1603                                         break;
1604                         str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1605                         draw_beginBoldFont();
1606                         drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1607                         draw_endBoldFont();
1608                         pos.y += 1.25 * hud_fontsize.y;
1609
1610                         pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1611                         pos.y += 1.25 * hud_fontsize.y;
1612
1613                         break;
1614                 }
1615         }
1616
1617         // Print info string
1618         float tl, fl, ll;
1619         str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1620         tl = STAT(TIMELIMIT);
1621         fl = STAT(FRAGLIMIT);
1622         ll = STAT(LEADLIMIT);
1623         if(gametype == MAPINFO_TYPE_LMS)
1624         {
1625                 if(tl > 0)
1626                         str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1627         }
1628         else
1629         {
1630                 if(tl > 0)
1631                         str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1632                 if(fl > 0)
1633                 {
1634                         if(tl > 0)
1635                                 str = strcat(str, _(" or"));
1636                         if(teamplay)
1637                         {
1638                                 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1639                                         (teamscores_label(ts_primary) == "score")   ? CTX(_("SCO^points")) :
1640                                         (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1641                                         TranslateScoresLabel(teamscores_label(ts_primary))));
1642                         }
1643                         else
1644                         {
1645                                 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1646                                         (scores_label(ps_primary) == "score")   ? CTX(_("SCO^points")) :
1647                                         (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1648                                         TranslateScoresLabel(scores_label(ps_primary))));
1649                         }
1650                 }
1651                 if(ll > 0)
1652                 {
1653                         if(tl > 0 || fl > 0)
1654                                 str = strcat(str, _(" or"));
1655                         if(teamplay)
1656                         {
1657                                 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1658                                         (teamscores_label(ts_primary) == "score")   ? CTX(_("SCO^points")) :
1659                                         (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1660                                         TranslateScoresLabel(teamscores_label(ts_primary))));
1661                         }
1662                         else
1663                         {
1664                                 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1665                                         (scores_label(ps_primary) == "score")   ? CTX(_("SCO^points")) :
1666                                         (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1667                                         TranslateScoresLabel(scores_label(ps_primary))));
1668                         }
1669                 }
1670         }
1671
1672         pos.y += 1.2 * hud_fontsize.y;
1673         drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1674
1675         // print information about respawn status
1676         float respawn_time = STAT(RESPAWN_TIME);
1677         if(!intermission)
1678         if(respawn_time)
1679         {
1680                 if(respawn_time < 0)
1681                 {
1682                         // a negative number means we are awaiting respawn, time value is still the same
1683                         respawn_time *= -1; // remove mark now that we checked it
1684
1685                         if(respawn_time < time) // it happens for a few frames when server is respawning the player
1686                                 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1687                         else
1688                                 str = sprintf(_("^1Respawning in ^3%s^1..."),
1689                                         (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1690                                                 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1691                                                 :
1692                                                 count_seconds(ceil(respawn_time - time))
1693                                         )
1694                                 );
1695                 }
1696                 else if(time < respawn_time)
1697                 {
1698                         str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1699                                 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1700                                         count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1701                                         :
1702                                         count_seconds(ceil(respawn_time - time))
1703                                 )
1704                         );
1705                 }
1706                 else if(time >= respawn_time)
1707                         str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1708
1709                 pos.y += 1.2 * hud_fontsize.y;
1710                 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1711         }
1712
1713         scoreboard_bottom = pos.y + 2 * hud_fontsize.y;
1714 }