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