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