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