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