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