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