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