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