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