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