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