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