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