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