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