]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/hud/panel/scoreboard.qc
Simplify some code
[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         FOREACH(Weapons, it != WEP_Null, {
1008                 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1009
1010                 WepSet set = it.m_wepset;
1011                 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1012                         ++disownedcnt;
1013         });
1014
1015         int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt;
1016         if (weapon_cnt <= 0) return pos;
1017
1018         int rows = 1;
1019         if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - 1) * 0.5))
1020                 rows = 2;
1021         int columnns = ceil(weapon_cnt / rows);
1022
1023         float height = 40;
1024
1025         drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1026         pos.y += 1.25 * hud_fontsize.y;
1027         if(panel.current_panel_bg != "0")
1028                 pos.y += panel_bg_border;
1029
1030         panel_pos = pos;
1031         panel_size.y = height * rows;
1032         panel_size.y += panel_bg_padding * 2;
1033         HUD_Panel_DrawBg();
1034
1035         vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1036         if(panel.current_panel_bg != "0")
1037                 end_pos.y += panel_bg_border * 2;
1038
1039         if(panel_bg_padding)
1040         {
1041                 panel_pos += '1 1 0' * panel_bg_padding;
1042                 panel_size -= '2 2 0' * panel_bg_padding;
1043         }
1044
1045         pos = panel_pos;
1046         vector tmp = panel_size;
1047
1048         float fontsize = height * 1/3;
1049         float weapon_height = height * 2/3;
1050         float weapon_width = tmp.x / columnns / rows;
1051
1052         if (sbt_bg_alpha)
1053                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1054
1055         if(sbt_highlight)
1056         {
1057                 // column highlighting
1058                 for (int i = 0; i < columnns; ++i)
1059                         if ((i % 2) == 0)
1060                                 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1061
1062                 // row highlighting
1063                 for (int i = 0; i < rows; ++i)
1064                         drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * fontsize, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1065         }
1066
1067         average_accuracy = 0;
1068         int weapons_with_stats = 0;
1069         if (rows == 2)
1070                 pos.x += weapon_width / 2;
1071
1072         if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1073                 rgb = '1 1 1';
1074         else
1075                 Accuracy_LoadColors();
1076
1077         float oldposx = pos.x;
1078         vector tmpos = pos;
1079
1080         int column = 0;
1081         FOREACH(Weapons, it != WEP_Null, {
1082                 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1083
1084                 WepSet set = it.m_wepset;
1085                 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1086                         continue;
1087
1088                 float weapon_alpha;
1089                 if (weapon_stats >= 0)
1090                         weapon_alpha = sbt_fg_alpha;
1091                 else
1092                         weapon_alpha = 0.2 * sbt_fg_alpha;
1093
1094                 // weapon icon
1095                 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1096                 // the accuracy
1097                 if (weapon_stats >= 0) {
1098                         weapons_with_stats += 1;
1099                         average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1100
1101                         string s;
1102                         s = sprintf("%d%%", weapon_stats * 100);
1103
1104                         float padding;
1105                         padding = (weapon_width - stringwidth(s, false, eX * fontsize)) / 2; // center the accuracy value
1106
1107                         if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1108                                 rgb = Accuracy_GetColor(weapon_stats);
1109
1110                         drawstring(tmpos + eX * padding + eY * weapon_height, s, '1 1 0' * fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1111                 }
1112                 tmpos.x += weapon_width * rows;
1113                 pos.x += weapon_width * rows;
1114                 if (rows == 2 && column == columnns - 1) {
1115                         tmpos.x = oldposx;
1116                         tmpos.y += height;
1117                         pos.y += height;
1118                 }
1119                 ++column;
1120         });
1121
1122         if (weapons_with_stats)
1123                 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1124
1125         panel_size.x += panel_bg_padding * 2; // restore initial width
1126         return end_pos;
1127 }
1128
1129 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1130         float px = pos.x;
1131         pos.x += hud_fontsize.x * 0.25;
1132         drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1133         pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1134         drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1135         pos.x = px;
1136         pos.y += hud_fontsize.y;
1137
1138         return pos;
1139 }
1140
1141 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1142         float stat_secrets_found, stat_secrets_total;
1143         float stat_monsters_killed, stat_monsters_total;
1144         float rows = 0;
1145         string val;
1146
1147         // get monster stats
1148         stat_monsters_killed = STAT(MONSTERS_KILLED);
1149         stat_monsters_total = STAT(MONSTERS_TOTAL);
1150
1151         // get secrets stats
1152         stat_secrets_found = STAT(SECRETS_FOUND);
1153         stat_secrets_total = STAT(SECRETS_TOTAL);
1154
1155         // get number of rows
1156         if(stat_secrets_total)
1157                 rows += 1;
1158         if(stat_monsters_total)
1159                 rows += 1;
1160
1161         // if no rows, return
1162         if (!rows)
1163                 return pos;
1164
1165         //  draw table header
1166         drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1167         pos.y += 1.25 * hud_fontsize.y;
1168         if(panel.current_panel_bg != "0")
1169                 pos.y += panel_bg_border;
1170
1171         panel_pos = pos;
1172         panel_size.y = hud_fontsize.y * rows;
1173         panel_size.y += panel_bg_padding * 2;
1174         HUD_Panel_DrawBg();
1175
1176         vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1177         if(panel.current_panel_bg != "0")
1178                 end_pos.y += panel_bg_border * 2;
1179
1180         if(panel_bg_padding)
1181         {
1182                 panel_pos += '1 1 0' * panel_bg_padding;
1183                 panel_size -= '2 2 0' * panel_bg_padding;
1184         }
1185
1186         pos = panel_pos;
1187         vector tmp = panel_size;
1188
1189         if (sbt_bg_alpha)
1190                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1191
1192         // draw monsters
1193         if(stat_monsters_total)
1194         {
1195                 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1196                 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1197         }
1198
1199         // draw secrets
1200         if(stat_secrets_total)
1201         {
1202                 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1203                 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1204         }
1205
1206         panel_size.x += panel_bg_padding * 2; // restore initial width
1207         return end_pos;
1208 }
1209
1210
1211 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1212 {
1213         int i;
1214         RANKINGS_RECEIVED_CNT = 0;
1215         for (i=RANKINGS_CNT-1; i>=0; --i)
1216                 if (grecordtime[i])
1217                         ++RANKINGS_RECEIVED_CNT;
1218
1219         if (RANKINGS_RECEIVED_CNT == 0)
1220                 return pos;
1221
1222         vector hl_rgb = rgb + '0.5 0.5 0.5';
1223
1224         pos.y += hud_fontsize.y;
1225         drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1226         pos.y += 1.25 * hud_fontsize.y;
1227         if(panel.current_panel_bg != "0")
1228                 pos.y += panel_bg_border;
1229
1230         panel_pos = pos;
1231         panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1232         panel_size.y += panel_bg_padding * 2;
1233         HUD_Panel_DrawBg();
1234
1235         vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1236         if(panel.current_panel_bg != "0")
1237                 end_pos.y += panel_bg_border * 2;
1238
1239         if(panel_bg_padding)
1240         {
1241                 panel_pos += '1 1 0' * panel_bg_padding;
1242                 panel_size -= '2 2 0' * panel_bg_padding;
1243         }
1244
1245         pos = panel_pos;
1246         vector tmp = panel_size;
1247
1248         if (sbt_bg_alpha)
1249                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1250
1251         // row highlighting
1252         for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1253         {
1254                 string n, p;
1255                 float t;
1256                 t = grecordtime[i];
1257                 if (t == 0)
1258                         continue;
1259                 n = grecordholder[i];
1260                 p = count_ordinal(i+1);
1261                 if(grecordholder[i] == entcs_GetName(player_localnum))
1262                         drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1263                 else if(!(i % 2) && sbt_highlight)
1264                         drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1265                 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1266                 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);
1267                 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, sbt_fg_alpha, DRAWFLAG_NORMAL);
1268                 pos.y += 1.25 * hud_fontsize.y;
1269         }
1270
1271         panel_size.x += panel_bg_padding * 2; // restore initial width
1272         return end_pos;
1273 }
1274
1275 void Scoreboard_Draw()
1276 {
1277         if(!autocvar__hud_configure)
1278         {
1279                 if(!hud_draw_maximized) return;
1280
1281                 // frametime checks allow to toggle the scoreboard even when the game is paused
1282                 if(scoreboard_active) {
1283                         if(hud_configure_menu_open == 1)
1284                                 scoreboard_fade_alpha = 1;
1285                         float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1286                         if (scoreboard_fadeinspeed && frametime)
1287                                 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1288                         else
1289                                 scoreboard_fade_alpha = 1;
1290                         if(hud_fontsize_str != autocvar_hud_fontsize)
1291                         {
1292                                 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1293                                 Scoreboard_initFieldSizes();
1294                                 if(hud_fontsize_str)
1295                                         strunzone(hud_fontsize_str);
1296                                 hud_fontsize_str = strzone(autocvar_hud_fontsize);
1297                         }
1298                 }
1299                 else {
1300                         float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1301                         if (scoreboard_fadeoutspeed && frametime)
1302                                 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1303                         else
1304                                 scoreboard_fade_alpha = 0;
1305                 }
1306
1307                 if (!scoreboard_fade_alpha)
1308                         return;
1309         }
1310         else
1311                 scoreboard_fade_alpha = 0;
1312
1313         if (autocvar_hud_panel_scoreboard_dynamichud)
1314                 HUD_Scale_Enable();
1315         else
1316                 HUD_Scale_Disable();
1317
1318         if(scoreboard_fade_alpha <= 0)
1319                 return;
1320         panel_fade_alpha *= scoreboard_fade_alpha;
1321         HUD_Panel_LoadCvars();
1322
1323         sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1324         sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1325         sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1326         sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1327         sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1328         sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1329
1330         // don't overlap with con_notify
1331         if(!autocvar__hud_configure)
1332                 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1333
1334         Scoreboard_UpdatePlayerTeams();
1335
1336         vector pos, tmp;
1337         entity pl, tm;
1338         string str;
1339
1340         // Initializes position
1341         pos = panel_pos;
1342
1343         // Heading
1344         vector sb_heading_fontsize;
1345         sb_heading_fontsize = hud_fontsize * 2;
1346         draw_beginBoldFont();
1347         drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1348         draw_endBoldFont();
1349
1350         pos.y += sb_heading_fontsize.y;
1351         if(panel.current_panel_bg != "0")
1352                 pos.y += panel_bg_border;
1353
1354         // Draw the scoreboard
1355         float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1356         if(scale <= 0)
1357                 scale = 0.25;
1358         vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1359
1360         if(teamplay)
1361         {
1362                 vector panel_bg_color_save = panel_bg_color;
1363                 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1364                 if(panel.current_panel_bg != "0")
1365                         team_score_baseoffset.x -= panel_bg_border;
1366                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1367                 {
1368                         if(tm.team == NUM_SPECTATOR)
1369                                 continue;
1370                         if(!tm.team)
1371                                 continue;
1372
1373                         draw_beginBoldFont();
1374                         vector rgb = Team_ColorRGB(tm.team);
1375                         str = ftos(tm.(teamscores(ts_primary)));
1376                         drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1377
1378                         if(ts_primary != ts_secondary)
1379                         {
1380                                 str = ftos(tm.(teamscores(ts_secondary)));
1381                                 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);
1382                         }
1383                         draw_endBoldFont();
1384                         if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1385                                 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1386                         else if(panel_bg_color_team > 0)
1387                                 panel_bg_color = rgb * panel_bg_color_team;
1388                         else
1389                                 panel_bg_color = rgb;
1390                         pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1391                 }
1392                 panel_bg_color = panel_bg_color_save;
1393         }
1394         else
1395         {
1396                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1397                 {
1398                         if(tm.team == NUM_SPECTATOR)
1399                                 continue;
1400
1401                         pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1402                 }
1403         }
1404
1405         if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1406                 if(race_speedaward) {
1407                         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);
1408                         pos.y += 1.25 * hud_fontsize.y;
1409                 }
1410                 if(race_speedaward_alltimebest) {
1411                         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);
1412                         pos.y += 1.25 * hud_fontsize.y;
1413                 }
1414                 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1415         }
1416         else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1417                 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1418
1419         pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1420
1421         // List spectators
1422         float specs = 0;
1423         tmp = pos;
1424         for(pl = players.sort_next; pl; pl = pl.sort_next)
1425         {
1426                 if(pl.team != NUM_SPECTATOR)
1427                         continue;
1428                 pos.y += 1.25 * hud_fontsize.y;
1429                 Scoreboard_DrawItem(pos, '0 0 0', pl, (pl.sv_entnum == player_localnum), specs);
1430                 ++specs;
1431         }
1432
1433         if(specs)
1434         {
1435                 draw_beginBoldFont();
1436                 drawstring(tmp, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1437                 draw_endBoldFont();
1438                 pos.y += 1.25 * hud_fontsize.y;
1439         }
1440
1441         // Print info string
1442         float tl, fl, ll;
1443         str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1444         tl = STAT(TIMELIMIT);
1445         fl = STAT(FRAGLIMIT);
1446         ll = STAT(LEADLIMIT);
1447         if(gametype == MAPINFO_TYPE_LMS)
1448         {
1449                 if(tl > 0)
1450                         str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1451         }
1452         else
1453         {
1454                 if(tl > 0)
1455                         str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1456                 if(fl > 0)
1457                 {
1458                         if(tl > 0)
1459                                 str = strcat(str, _(" or"));
1460                         if(teamplay)
1461                         {
1462                                 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1463                                         (teamscores_label(ts_primary) == "score")   ? CTX(_("SCO^points")) :
1464                                         (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1465                                         TranslateScoresLabel(teamscores_label(ts_primary))));
1466                         }
1467                         else
1468                         {
1469                                 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1470                                         (scores_label(ps_primary) == "score")   ? CTX(_("SCO^points")) :
1471                                         (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1472                                         TranslateScoresLabel(scores_label(ps_primary))));
1473                         }
1474                 }
1475                 if(ll > 0)
1476                 {
1477                         if(tl > 0 || fl > 0)
1478                                 str = strcat(str, _(" or"));
1479                         if(teamplay)
1480                         {
1481                                 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1482                                         (teamscores_label(ts_primary) == "score")   ? CTX(_("SCO^points")) :
1483                                         (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1484                                         TranslateScoresLabel(teamscores_label(ts_primary))));
1485                         }
1486                         else
1487                         {
1488                                 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1489                                         (scores_label(ps_primary) == "score")   ? CTX(_("SCO^points")) :
1490                                         (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1491                                         TranslateScoresLabel(scores_label(ps_primary))));
1492                         }
1493                 }
1494         }
1495
1496         pos.y += 1.2 * hud_fontsize.y;
1497         drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1498
1499         // print information about respawn status
1500         float respawn_time = STAT(RESPAWN_TIME);
1501         if(!intermission)
1502         if(respawn_time)
1503         {
1504                 if(respawn_time < 0)
1505                 {
1506                         // a negative number means we are awaiting respawn, time value is still the same
1507                         respawn_time *= -1; // remove mark now that we checked it
1508
1509                         if(respawn_time < time) // it happens for a few frames when server is respawning the player
1510                                 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1511                         else
1512                                 str = sprintf(_("^1Respawning in ^3%s^1..."),
1513                                         (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1514                                                 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1515                                                 :
1516                                                 count_seconds(ceil(respawn_time - time))
1517                                         )
1518                                 );
1519                 }
1520                 else if(time < respawn_time)
1521                 {
1522                         str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1523                                 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1524                                         count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1525                                         :
1526                                         count_seconds(ceil(respawn_time - time))
1527                                 )
1528                         );
1529                 }
1530                 else if(time >= respawn_time)
1531                         str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1532
1533                 pos.y += 1.2 * hud_fontsize.y;
1534                 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1535         }
1536
1537         scoreboard_bottom = pos.y + 2 * hud_fontsize.y;
1538 }