]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/hud/panel/scoreboard.qc
Always draw the scoreboard on top
[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         if(is_spec && !is_self)
813                 rgb = '0 0 0';
814
815         vector h_pos = item_pos;
816         vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
817         // alternated rows highlighting
818         if(is_self)
819                 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
820         else if((sbt_highlight) && (!(pl_number % 2)))
821                 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
822
823         vector pos = item_pos;
824         pos.x += hud_fontsize.x * 0.5;
825         pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
826         vector tmp = '0 0 0';
827         int i;
828         PlayerScoreField field;
829         for(i = 0; i < sbt_num_fields; ++i)
830         {
831                 field = sbt_field[i];
832                 if(field == SP_SEPARATOR)
833                         break;
834
835                 if(is_spec && field != SP_NAME && field != SP_PING) {
836                         pos.x += sbt_field_size[i] + hud_fontsize.x;
837                         continue;
838                 }
839                 str = Scoreboard_GetField(pl, field);
840                 str = Scoreboard_FixColumnWidth(i, str);
841
842                 pos.x += sbt_field_size[i] + hud_fontsize.x;
843
844                 if(field == SP_NAME) {
845                         tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
846                         if (is_self)
847                                 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
848                         else
849                                 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
850                 } else {
851                         tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
852                         if (is_self)
853                                 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
854                         else
855                                 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
856                 }
857
858                 tmp.x = sbt_field_size[i] + hud_fontsize.x;
859                 if(sbt_field_icon0 != "")
860                         if (is_self)
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_self, DRAWFLAG_NORMAL);
862                         else
863                                 drawpic(pos - tmp, sbt_field_icon0, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon0_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
864                 if(sbt_field_icon1 != "")
865                         if (is_self)
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_self, DRAWFLAG_NORMAL);
867                         else
868                                 drawpic(pos - tmp, sbt_field_icon1, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon1_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
869                 if(sbt_field_icon2 != "")
870                         if (is_self)
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_self, DRAWFLAG_NORMAL);
872                         else
873                                 drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, sbt_field_icon2_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
874         }
875
876         if(sbt_field[i] == SP_SEPARATOR)
877         {
878                 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
879                 for(i = sbt_num_fields-1; i > 0; --i)
880                 {
881                         field = sbt_field[i];
882                         if(field == SP_SEPARATOR)
883                                 break;
884
885                         if(is_spec && field != SP_NAME && field != SP_PING) {
886                                 pos.x -= sbt_field_size[i] + hud_fontsize.x;
887                                 continue;
888                         }
889
890                         str = Scoreboard_GetField(pl, field);
891                         str = Scoreboard_FixColumnWidth(i, str);
892
893                         if(field == SP_NAME) {
894                                 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
895                                 if(is_self)
896                                         drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
897                                 else
898                                         drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
899                         } else {
900                                 tmp.x = sbt_fixcolumnwidth_len;
901                                 if(is_self)
902                                         drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
903                                 else
904                                         drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
905                         }
906
907                         tmp.x = sbt_field_size[i];
908                         if(sbt_field_icon0 != "")
909                                 if (is_self)
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_self, DRAWFLAG_NORMAL);
911                                 else
912                                         drawpic(pos - tmp, sbt_field_icon0, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon0_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
913                         if(sbt_field_icon1 != "")
914                                 if (is_self)
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_self, DRAWFLAG_NORMAL);
916                                 else
917                                         drawpic(pos - tmp, sbt_field_icon1, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon1_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
918                         if(sbt_field_icon2 != "")
919                                 if (is_self)
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_self, DRAWFLAG_NORMAL);
921                                 else
922                                         drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, sbt_field_icon2_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
923                         pos.x -= sbt_field_size[i] + hud_fontsize.x;
924                 }
925         }
926
927         if(pl.eliminated)
928                 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
929 }
930
931 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
932 {
933         entity pl;
934
935         panel_pos = pos;
936         panel_size.y = 1.25 * hud_fontsize.y * (1 + max(1, tm.team_size));
937         panel_size.y += panel_bg_padding * 2;
938         HUD_Panel_DrawBg();
939
940         vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
941         if(panel.current_panel_bg != "0")
942                 end_pos.y += panel_bg_border * 2;
943
944         if(panel_bg_padding)
945         {
946                 panel_pos += '1 1 0' * panel_bg_padding;
947                 panel_size -= '2 2 0' * panel_bg_padding;
948         }
949
950         pos = panel_pos;
951         vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
952
953         // rounded header
954         if (sbt_bg_alpha)
955                 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
956
957         pos.y += 1.25 * hud_fontsize.y;
958
959         // table background
960         tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
961         if (sbt_bg_alpha)
962                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
963
964
965         // print header row and highlight columns
966         pos = Scoreboard_DrawHeader(panel_pos, rgb);
967
968         // fill the table and draw the rows
969         int i = 0;
970         if (teamplay)
971                 for(pl = players.sort_next; pl; pl = pl.sort_next)
972                 {
973                         if(pl.team != tm.team)
974                                 continue;
975                         Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
976                         pos.y += 1.25 * hud_fontsize.y;
977                         ++i;
978                 }
979         else
980                 for(pl = players.sort_next; pl; pl = pl.sort_next)
981                 {
982                         if(pl.team == NUM_SPECTATOR)
983                                 continue;
984                         Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
985                         pos.y += 1.25 * hud_fontsize.y;
986                         ++i;
987                 }
988
989         panel_size.x += panel_bg_padding * 2; // restore initial width
990         return end_pos;
991 }
992
993 float Scoreboard_WouldDraw() {
994         if (QuickMenu_IsOpened())
995                 return 0;
996         else if (HUD_Radar_Clickable())
997                 return 0;
998         else if (scoreboard_showscores)
999                 return 1;
1000         else if (intermission == 1)
1001                 return 1;
1002         else if (intermission == 2)
1003                 return 0;
1004         else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1005                 return 1;
1006         else if (scoreboard_showscores_force)
1007                 return 1;
1008         return 0;
1009 }
1010
1011 float average_accuracy;
1012 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1013 {
1014         WepSet weapons_stat = WepSet_GetFromStat();
1015         WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1016         int disownedcnt = 0;
1017         FOREACH(Weapons, it != WEP_Null, {
1018                 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1019
1020                 WepSet set = it.m_wepset;
1021                 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1022                         ++disownedcnt;
1023         });
1024
1025         int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt;
1026         if (weapon_cnt <= 0) return pos;
1027
1028         int rows = 1;
1029         if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - 1) * 0.5))
1030                 rows = 2;
1031         int columnns = ceil(weapon_cnt / rows);
1032
1033         float height = 40;
1034
1035         drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1036         pos.y += 1.25 * hud_fontsize.y;
1037         if(panel.current_panel_bg != "0")
1038                 pos.y += panel_bg_border;
1039
1040         panel_pos = pos;
1041         panel_size.y = height * rows;
1042         panel_size.y += panel_bg_padding * 2;
1043         HUD_Panel_DrawBg();
1044
1045         vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1046         if(panel.current_panel_bg != "0")
1047                 end_pos.y += panel_bg_border * 2;
1048
1049         if(panel_bg_padding)
1050         {
1051                 panel_pos += '1 1 0' * panel_bg_padding;
1052                 panel_size -= '2 2 0' * panel_bg_padding;
1053         }
1054
1055         pos = panel_pos;
1056         vector tmp = panel_size;
1057
1058         float fontsize = height * 1/3;
1059         float weapon_height = height * 2/3;
1060         float weapon_width = tmp.x / columnns / rows;
1061
1062         if (sbt_bg_alpha)
1063                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1064
1065         if(sbt_highlight)
1066         {
1067                 // column highlighting
1068                 for (int i = 0; i < columnns; ++i)
1069                         if ((i % 2) == 0)
1070                                 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1071
1072                 // row highlighting
1073                 for (int i = 0; i < rows; ++i)
1074                         drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * fontsize, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1075         }
1076
1077         average_accuracy = 0;
1078         int weapons_with_stats = 0;
1079         if (rows == 2)
1080                 pos.x += weapon_width / 2;
1081
1082         if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1083                 rgb = '1 1 1';
1084         else
1085                 Accuracy_LoadColors();
1086
1087         float oldposx = pos.x;
1088         vector tmpos = pos;
1089
1090         int column = 0;
1091         FOREACH(Weapons, it != WEP_Null, {
1092                 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1093
1094                 WepSet set = it.m_wepset;
1095                 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1096                         continue;
1097
1098                 float weapon_alpha;
1099                 if (weapon_stats >= 0)
1100                         weapon_alpha = sbt_fg_alpha;
1101                 else
1102                         weapon_alpha = 0.2 * sbt_fg_alpha;
1103
1104                 // weapon icon
1105                 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1106                 // the accuracy
1107                 if (weapon_stats >= 0) {
1108                         weapons_with_stats += 1;
1109                         average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1110
1111                         string s;
1112                         s = sprintf("%d%%", weapon_stats * 100);
1113
1114                         float padding;
1115                         padding = (weapon_width - stringwidth(s, false, eX * fontsize)) / 2; // center the accuracy value
1116
1117                         if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1118                                 rgb = Accuracy_GetColor(weapon_stats);
1119
1120                         drawstring(tmpos + eX * padding + eY * weapon_height, s, '1 1 0' * fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1121                 }
1122                 tmpos.x += weapon_width * rows;
1123                 pos.x += weapon_width * rows;
1124                 if (rows == 2 && column == columnns - 1) {
1125                         tmpos.x = oldposx;
1126                         tmpos.y += height;
1127                         pos.y += height;
1128                 }
1129                 ++column;
1130         });
1131
1132         if (weapons_with_stats)
1133                 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1134
1135         panel_size.x += panel_bg_padding * 2; // restore initial width
1136         return end_pos;
1137 }
1138
1139 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1140         float px = pos.x;
1141         pos.x += hud_fontsize.x * 0.25;
1142         drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1143         pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1144         drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1145         pos.x = px;
1146         pos.y += hud_fontsize.y;
1147
1148         return pos;
1149 }
1150
1151 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1152         float stat_secrets_found, stat_secrets_total;
1153         float stat_monsters_killed, stat_monsters_total;
1154         float rows = 0;
1155         string val;
1156
1157         // get monster stats
1158         stat_monsters_killed = STAT(MONSTERS_KILLED);
1159         stat_monsters_total = STAT(MONSTERS_TOTAL);
1160
1161         // get secrets stats
1162         stat_secrets_found = STAT(SECRETS_FOUND);
1163         stat_secrets_total = STAT(SECRETS_TOTAL);
1164
1165         // get number of rows
1166         if(stat_secrets_total)
1167                 rows += 1;
1168         if(stat_monsters_total)
1169                 rows += 1;
1170
1171         // if no rows, return
1172         if (!rows)
1173                 return pos;
1174
1175         //  draw table header
1176         drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1177         pos.y += 1.25 * hud_fontsize.y;
1178         if(panel.current_panel_bg != "0")
1179                 pos.y += panel_bg_border;
1180
1181         panel_pos = pos;
1182         panel_size.y = hud_fontsize.y * rows;
1183         panel_size.y += panel_bg_padding * 2;
1184         HUD_Panel_DrawBg();
1185
1186         vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1187         if(panel.current_panel_bg != "0")
1188                 end_pos.y += panel_bg_border * 2;
1189
1190         if(panel_bg_padding)
1191         {
1192                 panel_pos += '1 1 0' * panel_bg_padding;
1193                 panel_size -= '2 2 0' * panel_bg_padding;
1194         }
1195
1196         pos = panel_pos;
1197         vector tmp = panel_size;
1198
1199         if (sbt_bg_alpha)
1200                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1201
1202         // draw monsters
1203         if(stat_monsters_total)
1204         {
1205                 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1206                 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1207         }
1208
1209         // draw secrets
1210         if(stat_secrets_total)
1211         {
1212                 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1213                 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1214         }
1215
1216         panel_size.x += panel_bg_padding * 2; // restore initial width
1217         return end_pos;
1218 }
1219
1220
1221 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1222 {
1223         int i;
1224         RANKINGS_RECEIVED_CNT = 0;
1225         for (i=RANKINGS_CNT-1; i>=0; --i)
1226                 if (grecordtime[i])
1227                         ++RANKINGS_RECEIVED_CNT;
1228
1229         if (RANKINGS_RECEIVED_CNT == 0)
1230                 return pos;
1231
1232         vector hl_rgb = rgb + '0.5 0.5 0.5';
1233
1234         pos.y += hud_fontsize.y;
1235         drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1236         pos.y += 1.25 * hud_fontsize.y;
1237         if(panel.current_panel_bg != "0")
1238                 pos.y += panel_bg_border;
1239
1240         panel_pos = pos;
1241         panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1242         panel_size.y += panel_bg_padding * 2;
1243         HUD_Panel_DrawBg();
1244
1245         vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1246         if(panel.current_panel_bg != "0")
1247                 end_pos.y += panel_bg_border * 2;
1248
1249         if(panel_bg_padding)
1250         {
1251                 panel_pos += '1 1 0' * panel_bg_padding;
1252                 panel_size -= '2 2 0' * panel_bg_padding;
1253         }
1254
1255         pos = panel_pos;
1256         vector tmp = panel_size;
1257
1258         if (sbt_bg_alpha)
1259                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1260
1261         // row highlighting
1262         for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1263         {
1264                 string n, p;
1265                 float t;
1266                 t = grecordtime[i];
1267                 if (t == 0)
1268                         continue;
1269                 n = grecordholder[i];
1270                 p = count_ordinal(i+1);
1271                 if(grecordholder[i] == entcs_GetName(player_localnum))
1272                         drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1273                 else if(!(i % 2) && sbt_highlight)
1274                         drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1275                 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1276                 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);
1277                 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, sbt_fg_alpha, DRAWFLAG_NORMAL);
1278                 pos.y += 1.25 * hud_fontsize.y;
1279         }
1280
1281         panel_size.x += panel_bg_padding * 2; // restore initial width
1282         return end_pos;
1283 }
1284
1285 void Scoreboard_Draw()
1286 {
1287         if(!autocvar__hud_configure)
1288         {
1289                 if(!hud_draw_maximized) return;
1290
1291                 // frametime checks allow to toggle the scoreboard even when the game is paused
1292                 if(scoreboard_active) {
1293                         if(hud_configure_menu_open == 1)
1294                                 scoreboard_fade_alpha = 1;
1295                         float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1296                         if (scoreboard_fadeinspeed && frametime)
1297                                 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1298                         else
1299                                 scoreboard_fade_alpha = 1;
1300                 }
1301                 else {
1302                         float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1303                         if (scoreboard_fadeoutspeed && frametime)
1304                                 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1305                         else
1306                                 scoreboard_fade_alpha = 0;
1307                 }
1308
1309                 if (!scoreboard_fade_alpha)
1310                         return;
1311         }
1312         else
1313                 scoreboard_fade_alpha = 0;
1314
1315         if (autocvar_hud_panel_scoreboard_dynamichud)
1316                 HUD_Scale_Enable();
1317         else
1318                 HUD_Scale_Disable();
1319
1320         if(scoreboard_fade_alpha <= 0)
1321                 return;
1322         HUD_Panel_UpdateCvars(scoreboard_fade_alpha);
1323
1324         sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1325         sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1326         sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1327         sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1328         sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1329         sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1330
1331         // don't overlap with con_notify
1332         if(!autocvar__hud_configure)
1333                 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1334
1335         Scoreboard_UpdatePlayerTeams();
1336
1337         vector pos, tmp;
1338         entity pl, tm;
1339         string str;
1340
1341         // Initializes position
1342         pos = panel_pos;
1343
1344         // Heading
1345         vector sb_heading_fontsize;
1346         sb_heading_fontsize = hud_fontsize * 2;
1347         draw_beginBoldFont();
1348         drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1349         draw_endBoldFont();
1350
1351         pos.y += sb_heading_fontsize.y;
1352         if(panel.current_panel_bg != "0")
1353                 pos.y += panel_bg_border;
1354
1355         // Draw the scoreboard
1356         float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1357         if(scale <= 0)
1358                 scale = 0.25;
1359         vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1360
1361         if(teamplay)
1362         {
1363                 vector panel_bg_color_save = panel_bg_color;
1364                 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1365                 if(panel.current_panel_bg != "0")
1366                         team_score_baseoffset.x -= panel_bg_border;
1367                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1368                 {
1369                         if(tm.team == NUM_SPECTATOR)
1370                                 continue;
1371                         if(!tm.team)
1372                                 continue;
1373
1374                         draw_beginBoldFont();
1375                         vector rgb = Team_ColorRGB(tm.team);
1376                         str = ftos(tm.(teamscores(ts_primary)));
1377                         drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1378
1379                         if(ts_primary != ts_secondary)
1380                         {
1381                                 str = ftos(tm.(teamscores(ts_secondary)));
1382                                 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);
1383                         }
1384                         draw_endBoldFont();
1385                         if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1386                                 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1387                         else if(panel_bg_color_team > 0)
1388                                 panel_bg_color = rgb * panel_bg_color_team;
1389                         else
1390                                 panel_bg_color = rgb;
1391                         pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1392                 }
1393                 panel_bg_color = panel_bg_color_save;
1394         }
1395         else
1396         {
1397                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1398                 {
1399                         if(tm.team == NUM_SPECTATOR)
1400                                 continue;
1401
1402                         pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1403                 }
1404         }
1405
1406         if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1407                 if(race_speedaward) {
1408                         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);
1409                         pos.y += 1.25 * hud_fontsize.y;
1410                 }
1411                 if(race_speedaward_alltimebest) {
1412                         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);
1413                         pos.y += 1.25 * hud_fontsize.y;
1414                 }
1415                 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1416         }
1417         else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1418                 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1419
1420         pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1421
1422         // List spectators
1423         float specs = 0;
1424         tmp = pos;
1425         for(pl = players.sort_next; pl; pl = pl.sort_next)
1426         {
1427                 if(pl.team != NUM_SPECTATOR)
1428                         continue;
1429                 pos.y += 1.25 * hud_fontsize.y;
1430                 Scoreboard_DrawItem(pos, panel_bg_color, pl, (pl.sv_entnum == player_localnum), specs);
1431                 ++specs;
1432         }
1433
1434         if(specs)
1435         {
1436                 draw_beginBoldFont();
1437                 drawstring(tmp, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1438                 draw_endBoldFont();
1439                 pos.y += 1.25 * hud_fontsize.y;
1440         }
1441
1442         // Print info string
1443         float tl, fl, ll;
1444         str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1445         tl = STAT(TIMELIMIT);
1446         fl = STAT(FRAGLIMIT);
1447         ll = STAT(LEADLIMIT);
1448         if(gametype == MAPINFO_TYPE_LMS)
1449         {
1450                 if(tl > 0)
1451                         str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1452         }
1453         else
1454         {
1455                 if(tl > 0)
1456                         str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1457                 if(fl > 0)
1458                 {
1459                         if(tl > 0)
1460                                 str = strcat(str, _(" or"));
1461                         if(teamplay)
1462                         {
1463                                 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1464                                         (teamscores_label(ts_primary) == "score")   ? CTX(_("SCO^points")) :
1465                                         (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1466                                         TranslateScoresLabel(teamscores_label(ts_primary))));
1467                         }
1468                         else
1469                         {
1470                                 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1471                                         (scores_label(ps_primary) == "score")   ? CTX(_("SCO^points")) :
1472                                         (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1473                                         TranslateScoresLabel(scores_label(ps_primary))));
1474                         }
1475                 }
1476                 if(ll > 0)
1477                 {
1478                         if(tl > 0 || fl > 0)
1479                                 str = strcat(str, _(" or"));
1480                         if(teamplay)
1481                         {
1482                                 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1483                                         (teamscores_label(ts_primary) == "score")   ? CTX(_("SCO^points")) :
1484                                         (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1485                                         TranslateScoresLabel(teamscores_label(ts_primary))));
1486                         }
1487                         else
1488                         {
1489                                 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1490                                         (scores_label(ps_primary) == "score")   ? CTX(_("SCO^points")) :
1491                                         (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1492                                         TranslateScoresLabel(scores_label(ps_primary))));
1493                         }
1494                 }
1495         }
1496
1497         pos.y += 1.2 * hud_fontsize.y;
1498         drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1499
1500         // print information about respawn status
1501         float respawn_time = STAT(RESPAWN_TIME);
1502         if(!intermission)
1503         if(respawn_time)
1504         {
1505                 if(respawn_time < 0)
1506                 {
1507                         // a negative number means we are awaiting respawn, time value is still the same
1508                         respawn_time *= -1; // remove mark now that we checked it
1509
1510                         if(respawn_time < time) // it happens for a few frames when server is respawning the player
1511                                 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1512                         else
1513                                 str = sprintf(_("^1Respawning in ^3%s^1..."),
1514                                         (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1515                                                 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1516                                                 :
1517                                                 count_seconds(ceil(respawn_time - time))
1518                                         )
1519                                 );
1520                 }
1521                 else if(time < respawn_time)
1522                 {
1523                         str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1524                                 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1525                                         count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1526                                         :
1527                                         count_seconds(ceil(respawn_time - time))
1528                                 )
1529                         );
1530                 }
1531                 else if(time >= respawn_time)
1532                         str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1533
1534                 pos.y += 1.2 * hud_fontsize.y;
1535                 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1536         }
1537
1538         scoreboard_bottom = pos.y + 2 * hud_fontsize.y;
1539 }