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