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