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